@cldmv/slothlet 3.7.0 → 3.8.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 (32) hide show
  1. package/README.md +6 -5
  2. package/REFERENCE.md +2 -0
  3. package/dist/lib/builders/api_builder.mjs +1 -1
  4. package/dist/lib/handlers/module-manager.mjs +17 -0
  5. package/dist/lib/helpers/eventemitter-context.mjs +1 -1
  6. package/dist/lib/helpers/module-discovery.mjs +17 -0
  7. package/dist/lib/helpers/module-manifest-validator.mjs +17 -0
  8. package/dist/lib/helpers/module-sort.mjs +17 -0
  9. package/dist/lib/i18n/languages/de-de.json +23 -1
  10. package/dist/lib/i18n/languages/en-gb.json +23 -1
  11. package/dist/lib/i18n/languages/en-us.json +23 -1
  12. package/dist/lib/i18n/languages/es-es.json +23 -1
  13. package/dist/lib/i18n/languages/es-mx.json +23 -1
  14. package/dist/lib/i18n/languages/fr-fr.json +23 -1
  15. package/dist/lib/i18n/languages/hi-in.json +23 -1
  16. package/dist/lib/i18n/languages/ja-jp.json +23 -1
  17. package/dist/lib/i18n/languages/ko-kr.json +23 -1
  18. package/dist/lib/i18n/languages/pt-br.json +23 -1
  19. package/dist/lib/i18n/languages/ru-ru.json +23 -1
  20. package/dist/lib/i18n/languages/zh-cn.json +23 -1
  21. package/package.json +5 -9
  22. package/schemas/slothlet.module.schema.json +98 -0
  23. package/types/dist/lib/builders/api_builder.d.mts.map +1 -1
  24. package/types/dist/lib/handlers/module-manager.d.mts +29 -0
  25. package/types/dist/lib/handlers/module-manager.d.mts.map +1 -0
  26. package/types/dist/lib/helpers/eventemitter-context.d.mts.map +1 -1
  27. package/types/dist/lib/helpers/module-discovery.d.mts +8 -0
  28. package/types/dist/lib/helpers/module-discovery.d.mts.map +1 -0
  29. package/types/dist/lib/helpers/module-manifest-validator.d.mts +14 -0
  30. package/types/dist/lib/helpers/module-manifest-validator.d.mts.map +1 -0
  31. package/types/dist/lib/helpers/module-sort.d.mts +2 -0
  32. package/types/dist/lib/helpers/module-sort.d.mts.map +1 -0
package/README.md CHANGED
@@ -43,18 +43,19 @@ Every feature has been hardened with a comprehensive test suite - over **5,300 t
43
43
 
44
44
  ## ✨ What's New
45
45
 
46
- ### Latest: v3.7.0 (May 2026)
46
+ ### Latest: v3.8.0 (May 2026)
47
47
 
48
- - **Read-level permission gating** — the permission system now checks property _reads_ of data values, not just function _calls_. Until now a module exporting a `Buffer`, `TypedArray`, `Date`, or primitive left that value readable by any other module via `self.something.value` regardless of deny rules — the check fired at invocation, and a data value has no invocation step. Reading a terminal data value off a module API path is now enforced against the rule set exactly like a call, targeting its leaf path. This is **on by default** when `permissions` is configured (set `readGating: false` to opt out) — a `defaultPolicy: "deny"` config will now deny cross-module data reads unless an allow rule covers the path.
49
- - **Runtime-toggleable** — read gating can be flipped after instance creation via `api.slothlet.permissions.control.readGating(true|false)`. Namespace traversal stays ungated (no per-segment allow rules needed), callable functions remain call-gated, external user-code reads are exempt, and the self-call bypass still applies.
50
- - [View full v3.7.0 Changelog](./docs/changelog/v3/v3.7.0.md)
48
+ - **Module discovery + mount pipeline** — new `api.slothlet.api.modules.*` namespace composes subsystems shipped as separate npm packages into a host's api tree at runtime. Each module package ships a `slothlet.module.json` manifest declaring where it mounts; slothlet walks the filesystem, validates the manifests, and grafts each module onto the api tree. `discover` / `sort` / `addModule` / `addModules` / `addDiscovered` / `removeModule` plus a canonical JSON Schema at `schemas/slothlet.module.schema.json`.
49
+ - **Multi-version routing** — when a single `addModules` call receives two or more entries sharing a `packageName` at different `version`s, each routes through slothlet's existing `versionConfig` system: every version lands at `vMAJOR.<mountPath>` and the highest semver becomes the registered default. Both versioned and dispatched access work transparently.
50
+ - **Five new lifecycle events + new `metadata.getFor(path)` wrapper** — `modules:discover-start` / `-complete`, `modules:mount-start` / `-complete`, `modules:loaded` observe the full pipeline. `metadata.getFor(pathOrModuleId)` rounds out the path-based metadata API (symmetric with the existing `setFor` / `removeFor`).
51
+ - [View full v3.8.0 Changelog](./docs/changelog/v3/v3.8.0.md)
51
52
 
52
53
  ### Recent Releases
53
54
 
55
+ - **v3.7.0** (May 2026) — Read-level permission gating: data-value reads are now permission-checked alongside function calls; `defaultPolicy: "deny"` now blocks cross-module data reads unless an allow rule covers the path ([Changelog](./docs/changelog/v3/v3.7.0.md))
54
56
  - **v3.6.0** (May 2026) — `self.slothlet.lockCaller()` / `bind()` pin caller identity onto callbacks; hooks and `run`/`scope` callbacks keep caller identity ([Changelog](./docs/changelog/v3/v3.6.0.md))
55
57
  - **v3.5.1** (May 2026) — Binary buffers (`Buffer` / `TypedArray` / `DataView`) cross `self` unwrapped; relative imports work from `.ts` / `.mts` modules ([Changelog](./docs/changelog/v3/v3.5.1.md))
56
58
  - **v3.5.0** (May 2026) — TypeScript runtime imports (`self` / `context` / `instanceID`) work from `.ts` / `.mts`; `slothlet typegen` CLI + programmatic API; runtime `self.X = …` assignment now persists ([Changelog](./docs/changelog/v3/v3.5.0.md))
57
- - **v3.4.1** (May 2026) — Permission gating for all `api.slothlet.*` routes; metadata hardening against prototype-pollution and circular payloads ([Changelog](./docs/changelog/v3/v3.4.1.md))
58
59
 
59
60
  📚 **For complete version history and detailed release notes, see [docs/changelog/](./docs/changelog/) folder.**
60
61
 
package/REFERENCE.md CHANGED
@@ -13,6 +13,8 @@ Full documentation lives in the GitHub repository.
13
13
  | Hooks | [docs/HOOKS.md](https://github.com/CLDMV/slothlet/blob/master/docs/HOOKS.md) |
14
14
  | Context propagation | [docs/CONTEXT-PROPAGATION.md](https://github.com/CLDMV/slothlet/blob/master/docs/CONTEXT-PROPAGATION.md) |
15
15
  | Metadata | [docs/METADATA.md](https://github.com/CLDMV/slothlet/blob/master/docs/METADATA.md) |
16
+ | Module discovery & mount | [docs/MODULE-DISCOVERY.md](https://github.com/CLDMV/slothlet/blob/master/docs/MODULE-DISCOVERY.md) |
17
+ | Module manifest schema | [schemas/slothlet.module.schema.json](https://github.com/CLDMV/slothlet/blob/master/schemas/slothlet.module.schema.json) |
16
18
  | Permissions | [docs/PERMISSIONS.md](https://github.com/CLDMV/slothlet/blob/master/docs/PERMISSIONS.md) |
17
19
  | API versioning | [docs/VERSIONING.md](https://github.com/CLDMV/slothlet/blob/master/docs/VERSIONING.md) |
18
20
  | TypeScript | [docs/TYPESCRIPT.md](https://github.com/CLDMV/slothlet/blob/master/docs/TYPESCRIPT.md) |
@@ -14,4 +14,4 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import{AsyncResource}from"node:async_hooks";import{ComponentBase}from"@cldmv/slothlet/factories/component-base";import{TYPE_STATES}from"@cldmv/slothlet/handlers/unified-wrapper";import{getLanguage,initI18n,setLanguage,t,translate}from"@cldmv/slothlet/i18n";function _resolvePathOrModuleId(slothlet,pathOrModuleId){const history=slothlet.handlers?.apiManager?.state?.addHistory;if(history){let match=null;for(let i=history.length-1;i>=0;i--){const entry=history[i];if(entry?.moduleID===pathOrModuleId){match=entry;break}}if(match)return match.apiPath}return pathOrModuleId}function makeCopyOnWriteSelf(parentSelf){const overlay=new Map;const childViews=new Map;return new Proxy({},{get(_t,prop){if(overlay.has(prop))return overlay.get(prop);const real=parentSelf[prop];if(real!==null&&typeof real==="object"){let view=childViews.get(prop);if(view===void 0){view=makeCopyOnWriteSelf(real);childViews.set(prop,view)}return view}return real},set(_t,prop,value){overlay.set(prop,value);childViews.delete(prop);return true},has(_t,prop){return overlay.has(prop)||prop in parentSelf},deleteProperty(_t,prop){overlay.delete(prop);childViews.delete(prop);return true},ownKeys(){const keys=new Set(Reflect.ownKeys(parentSelf));for(const k of overlay.keys())keys.add(k);return[...keys]},getOwnPropertyDescriptor(_t,prop){if(overlay.has(prop)){return{value:overlay.get(prop),writable:true,enumerable:true,configurable:true}}const desc=Reflect.getOwnPropertyDescriptor(parentSelf,prop);return desc?{...desc,configurable:true}:void 0}})}class ApiBuilder extends ComponentBase{static slothletProperty="apiBuilder";constructor(slothlet){super(slothlet)}async buildFinalAPI(userApi){this.slothlet.debug("api",{key:"DEBUG_MODE_BUILD_FINAL_API_CALLED",diagnostics:this.____config.diagnostics,userApiKeys:Object.keys(userApi)});if(this.slothlet._ownBuiltins){for(const[key,ref]of Object.entries(this.slothlet._ownBuiltins)){if(ref&&Object.prototype.hasOwnProperty.call(userApi,key)&&userApi[key]===ref){try{delete userApi[key]}catch(_){}}}}this.slothlet.userHooks={shutdown:typeof userApi.shutdown==="function"?userApi.shutdown:null,destroy:typeof userApi.destroy==="function"?userApi.destroy:null};if(userApi.slothlet){new this.SlothletWarning("WARNING_RESERVED_PROPERTY_CONFLICT",{properties:"slothlet"})}const slothletNamespace=await this.createSlothletNamespace(userApi);this.slothlet.debug("api",{key:"DEBUG_MODE_SLOTHLET_NAMESPACE_CREATED",namespaceKeys:Object.keys(slothletNamespace),hasDiag:!!slothletNamespace.diag});const shutdownFn=this.createShutdownFunction();this.attachBuiltins(userApi,{slothlet:slothletNamespace,shutdown:shutdownFn,destroy:null});this.slothlet.debug("api",{key:"DEBUG_MODE_BUILT_INS_ATTACHED",userApiKeys:Object.keys(userApi),hasSlothlet:!!userApi.slothlet,hasDiag:!!userApi.slothlet?.diag});const destroyWithApi=this.createDestroyFunction(userApi);Object.defineProperty(userApi,"destroy",{value:destroyWithApi,enumerable:true,writable:false,configurable:true});this.slothlet._ownBuiltins={shutdown:shutdownFn,slothlet:slothletNamespace,destroy:destroyWithApi};return userApi}async createSlothletNamespace(userApi){const slothlet=this.slothlet;const config=this.____config;const enforceInternalPermission=targetPath=>{const ctx=slothlet.contextManager?.tryGetContext?.();const callerWrapper=ctx?.currentWrapper;if(!callerWrapper)return;const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.enforceAccess){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}const callerPath=callerWrapper.____slothletInternal?.apiPath??"";const callerFilePath=callerWrapper.____slothletInternal?.filePath??null;const runtimeContext=ctx?.context??null;if(!permissionManager.enforceAccess(callerPath,targetPath,callerFilePath,null,runtimeContext)){throw new slothlet.SlothletError("PERMISSION_DENIED",{caller:callerPath,target:targetPath})}};const canTraverseInternalNamespace=targetPath=>{const ctx=slothlet.contextManager?.tryGetContext?.();const callerWrapper=ctx?.currentWrapper;if(!callerWrapper)return false;const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.getRulesForCaller||!permissionManager?.checkAccess){return false}const callerPath=callerWrapper.____slothletInternal?.apiPath??"";const callerFilePath=callerWrapper.____slothletInternal?.filePath??null;const runtimeContext=ctx?.context??null;const callerRules=permissionManager.getRulesForCaller(callerPath);const conditionMatches=condition=>typeof permissionManager.matchesCondition==="function"?permissionManager.matchesCondition(condition,runtimeContext):false;const prefix=`${targetPath}.`;for(const rule of callerRules){if(!rule||rule.effect!=="allow"||typeof rule.target!=="string")continue;const couldMatchDescendant=rule.target.startsWith(prefix)||rule.target==="**"||rule.target==="*";if(!couldMatchDescendant)continue;if(rule.target.startsWith(prefix)&&conditionMatches(rule.condition)){return true}const probePaths=[`${targetPath}.__probe__`];if(rule.target.startsWith(prefix)){const suffix=rule.target.slice(prefix.length);const firstSegment=suffix.split(".")[0];if(firstSegment&&!/[*!?{}]/u.test(firstSegment)){probePaths.unshift(`${targetPath}.${firstSegment}`);probePaths.push(`${targetPath}.${firstSegment}.__probe__`)}}for(const probePath of probePaths){if(permissionManager.checkAccess(callerPath,probePath,callerFilePath,null,runtimeContext,{useCache:false})){return true}}}return false};const createInternalRouteProxy=(value,routePath,seen=new WeakMap)=>{if(!value||typeof value!=="object"&&typeof value!=="function")return value;const isMetaProperty=prop=>prop==="__proto__"||prop==="prototype"||prop==="constructor"||prop==="caller"||prop==="arguments";let routeCache=seen.get(value);if(!routeCache){routeCache=new Map;seen.set(value,routeCache)}if(routeCache.has(routePath)){return routeCache.get(routePath)}const proxy=new Proxy(value,{get(target,prop,receiver){if(typeof prop!=="string"){const result2=Reflect.get(target,prop,receiver);return createInternalRouteProxy(result2,routePath,seen)}const childRoutePath=`${routePath}.${prop}`;let deniedError=null;try{enforceInternalPermission(childRoutePath)}catch(error){deniedError=error}if(deniedError){if(canTraverseInternalNamespace(childRoutePath)){const result2=Reflect.get(target,prop,receiver);if(result2&&(typeof result2==="object"||typeof result2==="function")){return createInternalRouteProxy(result2,childRoutePath,seen)}}throw deniedError}const result=Reflect.get(target,prop,receiver);if(isMetaProperty(prop)){return result}const descriptor=Object.getOwnPropertyDescriptor(target,prop);if(descriptor&&"value"in descriptor&&descriptor.configurable===false&&descriptor.writable===false){return descriptor.value}return createInternalRouteProxy(result,childRoutePath,seen)},getOwnPropertyDescriptor(target,prop){const descriptor=Reflect.getOwnPropertyDescriptor(target,prop);if(!descriptor)return void 0;if(typeof prop!=="string"){return descriptor}const childRoutePath=`${routePath}.${prop}`;if(isMetaProperty(prop)){enforceInternalPermission(childRoutePath);return descriptor}if("get"in descriptor||"set"in descriptor){enforceInternalPermission(childRoutePath);if(descriptor.configurable===true){return{...descriptor,get:typeof descriptor.get==="function"?function slothlet_internal_descriptor_getter(...args){enforceInternalPermission(childRoutePath);return Reflect.apply(descriptor.get,this,args)}:descriptor.get,set:typeof descriptor.set==="function"?function slothlet_internal_descriptor_setter(...args){enforceInternalPermission(childRoutePath);return Reflect.apply(descriptor.set,this,args)}:descriptor.set}}return descriptor}if(!("value"in descriptor)){return descriptor}const descriptorValue=descriptor.value;if(descriptor.configurable===false&&descriptor.writable===false){enforceInternalPermission(childRoutePath);return descriptor}if(!descriptorValue||typeof descriptorValue!=="object"&&typeof descriptorValue!=="function"||typeof descriptorValue==="function"){enforceInternalPermission(childRoutePath);if(typeof descriptorValue==="function"&&descriptor.configurable===true){return{...descriptor,value:createInternalRouteProxy(descriptorValue,childRoutePath,seen)}}return descriptor}return{...descriptor,value:createInternalRouteProxy(descriptorValue,childRoutePath,seen)}},set(target,prop,newValue,receiver){if(typeof prop!=="string"){return Reflect.set(target,prop,newValue,receiver)}const childRoutePath=`${routePath}.${prop}`;enforceInternalPermission(childRoutePath);return Reflect.set(target,prop,newValue,receiver)},defineProperty(target,prop,descriptor){if(typeof prop!=="string"){return Reflect.defineProperty(target,prop,descriptor)}const childRoutePath=`${routePath}.${prop}`;enforceInternalPermission(childRoutePath);return Reflect.defineProperty(target,prop,descriptor)},deleteProperty(target,prop){if(typeof prop!=="string"){return Reflect.deleteProperty(target,prop)}const childRoutePath=`${routePath}.${prop}`;enforceInternalPermission(childRoutePath);return Reflect.deleteProperty(target,prop)},ownKeys(target){return Reflect.ownKeys(target)},apply(target,thisArg,argArray){enforceInternalPermission(routePath);return Reflect.apply(target,thisArg,argArray)},construct(target,argArray,newTarget){enforceInternalPermission(routePath);return Reflect.construct(target,argArray,newTarget)}});routeCache.set(routePath,proxy);return proxy};let version="unknown";try{const pkgPath=new URL("../../../package.json",import.meta.url);const{readFile}=await import("node:fs/promises");const pkgContent=await readFile(pkgPath,"utf-8");const pkg=JSON.parse(pkgContent);version=pkg.version||"unknown"}catch{}const namespace={i18n:{setLanguage,getLanguage,translate,t,initI18n},version,instanceID:slothlet.instanceID,types:TYPE_STATES,api:{add:async function slothlet_api_add(apiPath,folderPath,options={},versionConfig=null){if(!config.api?.mutations?.add){throw new slothlet.SlothletError("INVALID_CONFIG_MUTATIONS_DISABLED",{operation:"api.add",validationError:true})}const{recordHistory:____recordHistory,collisionMode:____collisionMode,mutateExisting:____mutateExisting,...filteredOptions}=options;return slothlet.handlers.apiManager.addApiComponent({apiPath,folderPath,options:filteredOptions,versionConfig:versionConfig||null})},remove:async function slothlet_api_remove(pathOrModuleId){if(!config.api?.mutations?.remove){throw new slothlet.SlothletError("INVALID_CONFIG_MUTATIONS_DISABLED",{operation:"api.remove",validationError:true})}if(typeof pathOrModuleId!=="string"){throw new slothlet.SlothletError("INVALID_ARGUMENT",{argument:"pathOrModuleId",expected:"string",received:typeof pathOrModuleId,validationError:true})}return slothlet.handlers.apiManager.removeApiComponent(pathOrModuleId)},reload:async function slothlet_api_reload(pathOrModuleId,options){if(!config.api?.mutations?.reload){throw new slothlet.SlothletError("INVALID_CONFIG_MUTATIONS_DISABLED",{operation:"api.reload",validationError:true})}if(pathOrModuleId==null||pathOrModuleId===""||pathOrModuleId==="."){return slothlet.handlers.apiManager.reloadApiComponent({apiPath:".",options})}if(typeof pathOrModuleId!=="string"){throw new slothlet.SlothletError("INVALID_ARGUMENT",{argument:"pathOrModuleId",expected:"string, null, undefined, or '.'",received:typeof pathOrModuleId,validationError:true})}const isModuleId=slothlet.handlers.apiManager.state.addHistory.some(entry=>entry.moduleID===pathOrModuleId);if(isModuleId){return slothlet.handlers.apiManager.reloadApiComponent({moduleID:pathOrModuleId,options})}const normalizedPath=slothlet.handlers.apiManager.normalizeApiPath(pathOrModuleId).apiPath;const pathParts=normalizedPath.split(".");let current=slothlet.api;for(const part of pathParts){if(!current||typeof current!=="object"&&typeof current!=="function"){throw new slothlet.SlothletError("INVALID_API_PATH",{apiPath:pathOrModuleId,validationError:true})}current=current[part]}if(current===void 0){throw new slothlet.SlothletError("INVALID_API_PATH",{apiPath:pathOrModuleId,validationError:true})}return slothlet.handlers.apiManager.reloadApiComponent({apiPath:normalizedPath,options})}},sanitize:function slothlet_sanitize(str){if(typeof str!=="string"){throw new slothlet.SlothletError("INVALID_ARGUMENT",{argument:"str",expected:"string",received:typeof str,validationError:true})}return slothlet.helpers.sanitize.sanitizePropertyName(str,slothlet.config.sanitize||{})},lockCaller:function slothlet_lockCaller(fn){if(typeof fn!=="function"){throw new slothlet.SlothletError("INVALID_ARGUMENT",{argument:"fn",expected:"function",received:typeof fn,validationError:true})}const capturedWrapper=slothlet.contextManager?.tryGetContext?.()?.currentWrapper??null;if(!capturedWrapper)return fn;const locked=function slothlet_lockedCaller(...args){return slothlet.contextManager.runInContext(slothlet.instanceID,fn,this,args,capturedWrapper,true)};locked._slothletOriginal=fn;return locked},bind:function slothlet_bind(fn){if(typeof fn!=="function"){throw new slothlet.SlothletError("INVALID_ARGUMENT",{argument:"fn",expected:"function",received:typeof fn,validationError:true})}return AsyncResource.bind(fn,"slothlet-bound")},context:{get:key=>{if(slothlet.contextManager.constructor.name==="LiveContextManager"){const currentID=slothlet.contextManager.currentInstanceID;if(currentID){const activeStore=slothlet.contextManager.instances.get(currentID);const isOurInstance=currentID===slothlet.instanceID||currentID?.startsWith(slothlet.instanceID+"__run_")||activeStore?.parentInstanceID===slothlet.instanceID;if(isOurInstance&&activeStore){return key?activeStore.context[key]:{...activeStore.context}}}const store=slothlet.contextManager.instances.get(slothlet.instanceID);if(!store){const baseContext2=slothlet.context||{};return key?baseContext2[key]:{...baseContext2}}return key?store.context[key]:{...store.context}}if(slothlet.contextManager.constructor.name==="AsyncContextManager"){let currentStore=slothlet.contextManager.tryGetContext();if(!currentStore){const baseStore2=slothlet.contextManager.instances.get(slothlet.instanceID);const baseContext3=baseStore2?.context||{};return key?baseContext3[key]:{...baseContext3}}const isOurInstance=currentStore.instanceID===slothlet.instanceID||currentStore.parentInstanceID===slothlet.instanceID||currentStore.instanceID.startsWith(slothlet.instanceID+"__run_");if(isOurInstance){return key?currentStore.context[key]:{...currentStore.context}}const baseStore=slothlet.contextManager.instances.get(slothlet.instanceID);const baseContext2=baseStore?.context||{};return key?baseContext2[key]:{...baseContext2}}const baseContext=slothlet.context||{};return key?baseContext[key]:{...baseContext}},diagnostics:()=>{if(!slothlet.config?.diagnostics)return void 0;const managerType=slothlet.contextManager.constructor.name;const result={instanceID:slothlet.instanceID,managerType,instancesMapSize:slothlet.contextManager.instances.size,instancesMapKeys:Array.from(slothlet.contextManager.instances.keys()),baseContext:slothlet.context};const store=slothlet.contextManager.instances.get(slothlet.instanceID);result.storeFromInstancesMap=store?{instanceID:store.instanceID,context:store.context,createdAt:store.createdAt}:null;if(managerType==="AsyncContextManager"){try{const currentCtx=slothlet.contextManager.tryGetContext();result.currentALSContext=currentCtx?{instanceID:currentCtx.instanceID,context:currentCtx.context,hasParent:!!currentCtx.parentContext,parentInstanceID:currentCtx.parentInstanceID}:null}catch(____error){result.currentALSContext=null}}if(managerType==="LiveContextManager"){result.currentInstanceID=slothlet.contextManager.currentInstanceID}return result},run:this.createRunFunction(),scope:this.createScopeFunction()},hook:{on:function slothlet_hook_on(typePattern,handler,options={}){if(!slothlet.handlers?.hookManager){throw new slothlet.SlothletError("HOOKS_NOT_INITIALIZED",{validationError:true})}return slothlet.handlers.hookManager.on(typePattern,handler,options)},remove:function slothlet_hook_remove(filter={}){if(!slothlet.handlers?.hookManager){throw new slothlet.SlothletError("HOOKS_NOT_INITIALIZED",{validationError:true})}return slothlet.handlers.hookManager.remove(filter)},clear:function slothlet_hook_clear(filter={}){return this.remove(filter)},off:function slothlet_hook_off(idOrFilter){if(!slothlet.handlers?.hookManager){throw new slothlet.SlothletError("HOOKS_NOT_INITIALIZED",{validationError:true})}const filter=typeof idOrFilter==="string"?{id:idOrFilter}:idOrFilter;return slothlet.handlers.hookManager.remove(filter)},enable:function slothlet_hook_enable(filter={}){if(!slothlet.handlers?.hookManager){throw new slothlet.SlothletError("HOOKS_NOT_INITIALIZED",{validationError:true})}return slothlet.handlers.hookManager.enable(filter)},disable:function slothlet_hook_disable(filter={}){if(!slothlet.handlers?.hookManager){throw new slothlet.SlothletError("HOOKS_NOT_INITIALIZED",{validationError:true})}return slothlet.handlers.hookManager.disable(filter)},list:function slothlet_hook_list(filter={}){if(!slothlet.handlers?.hookManager){throw new slothlet.SlothletError("HOOKS_NOT_INITIALIZED",{validationError:true})}return slothlet.handlers.hookManager.list(filter)}},metadata:{setGlobal:function slothlet_metadata_setGlobal(keyOrObj,value){if(!slothlet.handlers?.metadata){throw new slothlet.SlothletError("METADATA_NOT_AVAILABLE",{handlersKeys:slothlet.handlers?Object.keys(slothlet.handlers).join(", "):"undefined",validationError:true})}const validateGlobalMetadataKeyPath=keyPath=>{const blocked=new Set(["__proto__","prototype","constructor"]);if(typeof keyPath!=="string"||keyPath.length===0){throw new slothlet.SlothletError("INVALID_METADATA_KEY",{key:keyPath,type:typeof keyPath,expected:"non-empty string"})}const segments=keyPath.split(".");for(const segment of segments){if(!segment||blocked.has(segment)){throw new slothlet.SlothletError("INVALID_METADATA_KEY",{key:keyPath,type:typeof keyPath,expected:"safe dot-notation key without reserved segments"})}}};const normalizeGlobalMetadataObject=(source,prefix="",ancestors=new WeakSet)=>{if(ancestors.has(source)){throw new slothlet.SlothletError("INVALID_ARGUMENT",{argument:"keyOrObj",expected:"acyclic object",received:"circular reference",validationError:true})}ancestors.add(source);try{const normalized={};for(const[key,nestedValue]of Object.entries(source)){const fullKey=prefix?`${prefix}.${key}`:key;validateGlobalMetadataKeyPath(fullKey);const nestedProto=nestedValue&&typeof nestedValue==="object"?Object.getPrototypeOf(nestedValue):null;const isPlainNested=nestedProto===Object.prototype||nestedProto===null;if(nestedValue&&typeof nestedValue==="object"&&!Array.isArray(nestedValue)&&isPlainNested){normalized[key]=normalizeGlobalMetadataObject(nestedValue,fullKey,ancestors);continue}normalized[key]=nestedValue}return normalized}finally{ancestors.delete(source)}};if(keyOrObj&&typeof keyOrObj==="object"&&!Array.isArray(keyOrObj)){const normalizedMetadata=normalizeGlobalMetadataObject(keyOrObj);for(const[key,nestedValue]of Object.entries(normalizedMetadata)){slothlet.handlers.metadata.setGlobalMetadata(key,nestedValue)}return}if(typeof keyOrObj!=="string"){throw new slothlet.SlothletError("INVALID_ARGUMENT",{argument:"keyOrObj",expected:"string or object",received:typeof keyOrObj,validationError:true})}validateGlobalMetadataKeyPath(keyOrObj);return slothlet.handlers.metadata.setGlobalMetadata(keyOrObj,value)},set:function slothlet_metadata_set(fn,key,value){if(!slothlet.handlers?.metadata){throw new slothlet.SlothletError("METADATA_NOT_AVAILABLE",{handlersKeys:slothlet.handlers?Object.keys(slothlet.handlers).join(", "):"undefined",validationError:true})}return slothlet.handlers.metadata.setUserMetadata(fn,key,value)},remove:function slothlet_metadata_remove(fn,key){return slothlet.handlers.metadata.removeUserMetadata(fn,key)},setFor:function slothlet_metadata_setFor(pathOrModuleId,keyOrObj,value){if(!slothlet.handlers?.metadata){throw new slothlet.SlothletError("METADATA_NOT_AVAILABLE",{handlersKeys:slothlet.handlers?Object.keys(slothlet.handlers).join(", "):"undefined",validationError:true})}const resolvedPath=_resolvePathOrModuleId(slothlet,pathOrModuleId);return slothlet.handlers.metadata.setPathMetadata(resolvedPath,keyOrObj,value)},removeFor:function slothlet_metadata_removeFor(pathOrModuleId,key){if(!slothlet.handlers?.metadata)return;const resolvedPath=_resolvePathOrModuleId(slothlet,pathOrModuleId);return slothlet.handlers.metadata.removePathMetadata(resolvedPath,key)},setForVersion:function slothlet_metadata_setForVersion(logicalPath,versionTag,keyOrObj,value){if(!slothlet.handlers?.metadata){throw new slothlet.SlothletError("METADATA_NOT_AVAILABLE",{handlersKeys:slothlet.handlers?Object.keys(slothlet.handlers).join(", "):"undefined",validationError:true})}const info=slothlet.handlers?.versionManager?.list(logicalPath);if(!info||!info.versions?.[versionTag]){throw new slothlet.SlothletError("VERSION_NOT_FOUND",{version:versionTag,apiPath:logicalPath})}const{moduleID}=info.versions[versionTag];const resolvedPath=_resolvePathOrModuleId(slothlet,moduleID);return slothlet.handlers.metadata.setPathMetadata(resolvedPath,keyOrObj,value)},getForVersion:function slothlet_metadata_getForVersion(logicalPath,versionTag){if(!slothlet.handlers?.metadata)return{};const info=slothlet.handlers?.versionManager?.list(logicalPath);if(!info||!info.versions?.[versionTag])return{};const{moduleID}=info.versions[versionTag];const resolvedPath=_resolvePathOrModuleId(slothlet,moduleID);return slothlet.handlers.metadata.getPathMetadata(resolvedPath)}},scope:this.createScopeFunction(),run:this.createRunFunction(),reload:async(options={})=>{if(!config.api?.mutations?.reload){throw new slothlet.SlothletError("INVALID_CONFIG_MUTATIONS_DISABLED",{operation:"reload",validationError:true})}return slothlet.reload(options)},shutdown:async()=>{return slothlet.shutdown()},owner:{get:apiPath=>{if(slothlet.handlers?.ownership){return slothlet.handlers.ownership.getPathOwnership(apiPath)}return null}},materialize:(()=>{const mgr=slothlet.handlers?.materialize;const getMaterializedState=()=>{enforceInternalPermission("slothlet.materialize.materialized");return mgr?.materialized??false};const getMaterializeStats=()=>{enforceInternalPermission("slothlet.materialize.get");return mgr?mgr.get():{total:0,materialized:0,remaining:0,percentage:100}};const waitForMaterialization=async()=>{enforceInternalPermission("slothlet.materialize.wait");if(!mgr)return;return mgr.wait()};if(!mgr){return Object.freeze({get materialized(){return getMaterializedState()},get:getMaterializeStats,wait:waitForMaterialization})}return Object.freeze({get materialized(){return getMaterializedState()},get:getMaterializeStats,wait:waitForMaterialization})})(),lifecycle:(()=>{const handler=slothlet.handlers?.lifecycle;const noop=()=>{};if(!handler)return{on:noop,off:noop};return{on:handler.on.bind(handler),off:handler.off.bind(handler)}})(),env:slothlet.envSnapshot,versioning:{list:function slothlet_version_list(logicalPath){if(!slothlet.handlers?.versionManager)return void 0;return slothlet.handlers.versionManager.list(logicalPath)},setDefault:function slothlet_version_setDefault(logicalPath,versionTag){if(!slothlet.handlers?.versionManager)return;return slothlet.handlers.versionManager.setDefault(logicalPath,versionTag)},unregister:async function slothlet_version_unregister(logicalPath,versionTag){if(!slothlet.handlers?.versionManager)return false;const info=slothlet.handlers.versionManager.list(logicalPath);if(!info||!info.versions?.[versionTag])return false;const{moduleID:versionedModuleID}=info.versions[versionTag];await slothlet.handlers.apiManager.removeApiComponent(versionedModuleID);return true},getVersionMetadata:function slothlet_version_getVersionMetadata(logicalPath,versionTag){if(!slothlet.handlers?.versionManager)return void 0;return slothlet.handlers.versionManager.getVersionMetadataByPath(logicalPath,versionTag)},setVersionMetadata:function slothlet_version_setVersionMetadata(logicalPath,versionTag,patch){if(!slothlet.handlers?.versionManager)return;return slothlet.handlers.versionManager.setVersionMetadataByPath(logicalPath,versionTag,patch)}},permissions:{addRule:function slothlet_permissions_addRule(rule){if(!config.api?.mutations?.permissions){throw new slothlet.SlothletError("INVALID_CONFIG_MUTATIONS_DISABLED",{operation:"api.slothlet.permissions.addRule",validationError:true})}const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.addRule){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}const ruleId=permissionManager.addRule(rule,null);if(slothlet.handlers?.apiManager?.state?.operationHistory){slothlet.handlers.apiManager.state.operationHistory.push({type:"addPermissionRule",rule,ownerModuleID:null,ruleId,timestamp:Date.now()})}return ruleId},removeRule:function slothlet_permissions_removeRule(ruleId){if(!config.api?.mutations?.permissions){throw new slothlet.SlothletError("INVALID_CONFIG_MUTATIONS_DISABLED",{operation:"api.slothlet.permissions.removeRule",validationError:true})}const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.removeRule){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}const ctx=slothlet.contextManager?.tryGetContext?.();const currentWrapper=ctx?.currentWrapper;const callerModuleID=currentWrapper?.____slothletInternal?.moduleID??null;const result=slothlet.handlers.permissionManager.removeRule(ruleId,callerModuleID);if(result&&slothlet.handlers?.apiManager?.state?.operationHistory){slothlet.handlers.apiManager.state.operationHistory.push({type:"removePermissionRule",ruleId,callerModuleID,timestamp:Date.now()})}return result},self:{access:function slothlet_permissions_self_access(target){const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.checkAccess){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}const ctx=slothlet.contextManager?.tryGetContext?.();const currentWrapper=ctx?.currentWrapper;const callerPath=currentWrapper?.____slothletInternal?.apiPath??"";const callerFilePath=currentWrapper?.____slothletInternal?.filePath??null;const runtimeContext=ctx?.context??null;return slothlet.handlers.permissionManager.checkAccess(callerPath,target,callerFilePath,null,runtimeContext)},rules:function slothlet_permissions_self_rules(){const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.getRulesForCaller){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}const currentWrapper=slothlet.contextManager?.tryGetContext?.()?.currentWrapper;const callerPath=currentWrapper?.____slothletInternal?.apiPath??"";return slothlet.handlers.permissionManager.getRulesForCaller(callerPath)}},global:{checkAccess:function slothlet_permissions_global_checkAccess(caller,target){const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.checkAccess){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}const runtimeContext=slothlet.contextManager?.tryGetContext?.()?.context??null;return slothlet.handlers.permissionManager.checkAccess(caller,target,null,null,runtimeContext)},rulesForPath:function slothlet_permissions_global_rulesForPath(path){const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.getRulesForPath){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}return slothlet.handlers.permissionManager.getRulesForPath(path)},rulesByModule:function slothlet_permissions_global_rulesByModule(moduleID){const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.getRulesByModule){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}return slothlet.handlers.permissionManager.getRulesByModule(moduleID)}},control:{get enabled(){const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.isEnabled){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}return permissionManager.isEnabled()},enable:function slothlet_permissions_control_enable(){const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.enable){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}slothlet.handlers.permissionManager.enable()},disable:function slothlet_permissions_control_disable(){const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.disable){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}slothlet.handlers.permissionManager.disable()},get readGatingEnabled(){const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.isReadGatingEnabled){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}return permissionManager.isReadGatingEnabled()},readGating:function slothlet_permissions_control_readGating(value){const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.setReadGating){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}slothlet.handlers.permissionManager.setReadGating(value)}}}};if(!config.hook?.enabled&&config.diagnostics!==true){delete namespace.hooks}if(config.diagnostics===true){namespace.diag={describe:(showAll=false)=>{if(showAll){return{...userApi}}return Reflect.ownKeys(userApi)},reference:slothlet.reference||null,context:slothlet.context||{},inspect:()=>{return slothlet.getDiagnostics()},getAPI:()=>{return slothlet.getAPI()},getOwnership:()=>{return slothlet.getOwnership()},owner:{get:apiPath=>{if(slothlet.handlers?.ownership){return slothlet.handlers.ownership.getPathOwnership(apiPath)}return null}},caches:{get:()=>{if(slothlet.handlers?.apiCacheManager){return slothlet.handlers.apiCacheManager.getCacheDiagnostics()}return{totalCaches:0,caches:[]}},getAllModuleIDs:()=>{if(slothlet.handlers?.apiCacheManager){return slothlet.handlers.apiCacheManager.getAllModuleIDs()}return[]},has:moduleID=>{if(slothlet.handlers?.apiCacheManager){return slothlet.handlers.apiCacheManager.has(moduleID)}return false}},SlothletWarning:slothlet.SlothletWarning,hook:slothlet.handlers?.hookManager?{get enabled(){return slothlet.handlers.hookManager.enabled},compilePattern:pattern=>{return slothlet.handlers.hookManager.getCompilePatternForDiagnostics()(pattern)}}:void 0}}return createInternalRouteProxy(namespace,"slothlet")}createShutdownFunction(){const slothlet=this.slothlet;const shutdownFunction={shutdown:async()=>{if(slothlet.userHooks?.shutdown&&typeof slothlet.userHooks.shutdown==="function"){await slothlet.userHooks.shutdown()}return slothlet.shutdown()}}.shutdown;return shutdownFunction}createRunFunction(){const slothlet=this.slothlet;const scopeFunc=this.createScopeFunction();const runFunction={run:async(contextData,callback,...args)=>{if(slothlet.config.scope===false){throw new slothlet.SlothletError("SCOPE_DISABLED",{},null,{validationError:true})}if(!contextData||typeof contextData!=="object"){throw new slothlet.SlothletError("SCOPE_INVALID_CONTEXT",{received:typeof contextData},null,{validationError:true})}if(typeof callback!=="function"){throw new slothlet.SlothletError("SCOPE_INVALID_CALLBACK",{received:typeof callback},null,{validationError:true})}return scopeFunc({context:contextData,fn:callback,args,merge:slothlet.config.scope?.merge||"shallow",isolation:slothlet.config.scope?.isolation||"partial"})}}.run;return runFunction}createScopeFunction(){const slothlet=this.slothlet;const scopeFunction={scope:async options=>{if(slothlet.config.scope===false){throw new slothlet.SlothletError("SCOPE_DISABLED",{},null,{validationError:true})}if(!options||typeof options!=="object"){throw new slothlet.SlothletError("SCOPE_INVALID_OPTIONS",{received:typeof options},null,{validationError:true})}if(!options.fn||typeof options.fn!=="function"){throw new slothlet.SlothletError("SCOPE_INVALID_FN",{received:typeof options?.fn},null,{validationError:true})}if(!options.context||typeof options.context!=="object"){throw new slothlet.SlothletError("SCOPE_INVALID_CONTEXT_OBJECT",{received:typeof options?.context},null,{validationError:true})}const{context:contextData,fn,args=[],merge="shallow",isolation}=options;if(merge!=="shallow"&&merge!=="deep"){throw new slothlet.SlothletError("SCOPE_INVALID_MERGE_STRATEGY",{merge},null,{validationError:true})}const isolationMode=isolation||slothlet.config.scope?.isolation||"partial";if(isolationMode!=="partial"&&isolationMode!=="full"){throw new slothlet.SlothletError("SCOPE_INVALID_ISOLATION_MODE",{isolationMode},null,{validationError:true})}const contextManager=slothlet.contextManager;if(!contextManager){throw new slothlet.SlothletError("NO_CONTEXT_MANAGER",{validationError:true})}const{utilities}=slothlet.helpers;if(contextManager.constructor.name==="LiveContextManager"){let currentStore=null;const currentID=contextManager.currentInstanceID;if(currentID){const activeStore=contextManager.instances.get(currentID);const isOurContext=currentID===slothlet.instanceID||activeStore?.parentInstanceID===slothlet.instanceID||currentID.startsWith(slothlet.instanceID+"__run_");if(isOurContext){currentStore=activeStore}}if(!currentStore){currentStore=contextManager.instances.get(slothlet.instanceID)}if(!currentStore){throw new slothlet.SlothletError("CONTEXT_NOT_FOUND",{instanceID:slothlet.instanceID,availableInstances:[...contextManager.instances.keys()].join(", ")||"none",validationError:true})}let mergedContext;if(merge==="deep"){mergedContext=utilities.deepMerge(currentStore.context,contextData);mergedContext=structuredClone(mergedContext)}else{const clonedParent=structuredClone(currentStore.context);mergedContext={...clonedParent,...contextData}}const childInstanceID=`${slothlet.instanceID}__run_${Date.now()}_${Math.random().toString(36).slice(2,9)}`;const childStore={instanceID:childInstanceID,context:mergedContext,self:isolationMode==="full"?makeCopyOnWriteSelf(currentStore.self):currentStore.self,config:currentStore.config,createdAt:currentStore.createdAt,parentInstanceID:slothlet.instanceID,currentWrapper:currentStore.currentWrapper,callerWrapper:currentStore.callerWrapper,slothlet:currentStore.slothlet};contextManager.instances.set(childInstanceID,childStore);const previousInstanceID=contextManager.currentInstanceID;try{contextManager.currentInstanceID=childInstanceID;return await fn(...args)}finally{contextManager.currentInstanceID=previousInstanceID;contextManager.instances.delete(childInstanceID)}}if(contextManager.constructor.name==="AsyncContextManager"){let currentStore=null;const activeStore=contextManager.tryGetContext();if(activeStore){const isOurContext=activeStore.instanceID===slothlet.instanceID||activeStore.parentInstanceID===slothlet.instanceID||activeStore.instanceID.startsWith(slothlet.instanceID+"__run_");if(isOurContext){currentStore=activeStore}}if(!currentStore){const baseStore=contextManager.instances.get(slothlet.instanceID);if(!baseStore){throw new slothlet.SlothletError("CONTEXT_NOT_FOUND",{instanceID:slothlet.instanceID,availableInstances:[...contextManager.instances.keys()].join(", ")||"none",validationError:true})}currentStore=baseStore}let mergedContext;if(merge==="deep"){mergedContext=utilities.deepMerge(currentStore.context,contextData);mergedContext=structuredClone(mergedContext)}else{const clonedParent=structuredClone(currentStore.context);mergedContext={...clonedParent,...contextData}}const childInstanceID=`${slothlet.instanceID}__run_${Date.now()}_${Math.random().toString(36).slice(2,9)}`;const childStore={instanceID:childInstanceID,context:mergedContext,self:isolationMode==="full"?makeCopyOnWriteSelf(currentStore.self):currentStore.self,config:currentStore.config,createdAt:currentStore.createdAt,parentInstanceID:slothlet.instanceID,currentWrapper:currentStore.currentWrapper,callerWrapper:currentStore.callerWrapper,slothlet:currentStore.slothlet};contextManager.instances.set(childInstanceID,childStore);try{return await contextManager.als.run(childStore,async()=>{return await fn(...args)})}finally{contextManager.instances.delete(childInstanceID)}}throw new slothlet.SlothletError("UNSUPPORTED_CONTEXT_MANAGER",{manager:contextManager.constructor.name,validationError:true})}}.scope;return scopeFunction}createDestroyFunction(api){const slothlet=this.slothlet;const destroyFunction={destroy:async()=>{if(slothlet.userHooks?.destroy&&typeof slothlet.userHooks.destroy==="function"){await slothlet.userHooks.destroy()}if(api&&typeof api.shutdown==="function"){await api.shutdown()}else{await slothlet.shutdown()}slothlet.isDestroyed=true;const objectsToClear=[api,slothlet.api].filter(obj=>obj&&typeof obj==="object");for(const obj of objectsToClear){const keys=Object.keys(obj);for(const key of keys){try{delete obj[key]}catch(_){}}}slothlet.api=null}}.destroy;return destroyFunction}attachBuiltins(userApi,builtins){Object.defineProperty(userApi,"slothlet",{value:builtins.slothlet,enumerable:true,writable:false,configurable:true});Object.defineProperty(userApi,"shutdown",{value:builtins.shutdown,enumerable:true,writable:false,configurable:true});if(builtins.destroy!==null){Object.defineProperty(userApi,"destroy",{value:builtins.destroy,enumerable:true,writable:false,configurable:true})}}}export{ApiBuilder};
17
+ import{AsyncResource}from"node:async_hooks";import{ComponentBase}from"@cldmv/slothlet/factories/component-base";import{TYPE_STATES}from"@cldmv/slothlet/handlers/unified-wrapper";import{getLanguage,initI18n,setLanguage,t,translate}from"@cldmv/slothlet/i18n";function _resolvePathOrModuleId(slothlet,pathOrModuleId){const history=slothlet.handlers?.apiManager?.state?.addHistory;if(history){let match=null;for(let i=history.length-1;i>=0;i--){const entry=history[i];if(entry?.moduleID===pathOrModuleId){match=entry;break}}if(match)return match.apiPath}return pathOrModuleId}function makeCopyOnWriteSelf(parentSelf){const overlay=new Map;const childViews=new Map;return new Proxy({},{get(_t,prop){if(overlay.has(prop))return overlay.get(prop);const real=parentSelf[prop];if(real!==null&&typeof real==="object"){let view=childViews.get(prop);if(view===void 0){view=makeCopyOnWriteSelf(real);childViews.set(prop,view)}return view}return real},set(_t,prop,value){overlay.set(prop,value);childViews.delete(prop);return true},has(_t,prop){return overlay.has(prop)||prop in parentSelf},deleteProperty(_t,prop){overlay.delete(prop);childViews.delete(prop);return true},ownKeys(){const keys=new Set(Reflect.ownKeys(parentSelf));for(const k of overlay.keys())keys.add(k);return[...keys]},getOwnPropertyDescriptor(_t,prop){if(overlay.has(prop)){return{value:overlay.get(prop),writable:true,enumerable:true,configurable:true}}const desc=Reflect.getOwnPropertyDescriptor(parentSelf,prop);return desc?{...desc,configurable:true}:void 0}})}class ApiBuilder extends ComponentBase{static slothletProperty="apiBuilder";constructor(slothlet){super(slothlet)}async buildFinalAPI(userApi){this.slothlet.debug("api",{key:"DEBUG_MODE_BUILD_FINAL_API_CALLED",diagnostics:this.____config.diagnostics,userApiKeys:Object.keys(userApi)});if(this.slothlet._ownBuiltins){for(const[key,ref]of Object.entries(this.slothlet._ownBuiltins)){if(ref&&Object.prototype.hasOwnProperty.call(userApi,key)&&userApi[key]===ref){try{delete userApi[key]}catch(_){}}}}this.slothlet.userHooks={shutdown:typeof userApi.shutdown==="function"?userApi.shutdown:null,destroy:typeof userApi.destroy==="function"?userApi.destroy:null};if(userApi.slothlet){new this.SlothletWarning("WARNING_RESERVED_PROPERTY_CONFLICT",{properties:"slothlet"})}const slothletNamespace=await this.createSlothletNamespace(userApi);this.slothlet.debug("api",{key:"DEBUG_MODE_SLOTHLET_NAMESPACE_CREATED",namespaceKeys:Object.keys(slothletNamespace),hasDiag:!!slothletNamespace.diag});const shutdownFn=this.createShutdownFunction();this.attachBuiltins(userApi,{slothlet:slothletNamespace,shutdown:shutdownFn,destroy:null});this.slothlet.debug("api",{key:"DEBUG_MODE_BUILT_INS_ATTACHED",userApiKeys:Object.keys(userApi),hasSlothlet:!!userApi.slothlet,hasDiag:!!userApi.slothlet?.diag});const destroyWithApi=this.createDestroyFunction(userApi);Object.defineProperty(userApi,"destroy",{value:destroyWithApi,enumerable:true,writable:false,configurable:true});this.slothlet._ownBuiltins={shutdown:shutdownFn,slothlet:slothletNamespace,destroy:destroyWithApi};return userApi}async createSlothletNamespace(userApi){const slothlet=this.slothlet;const config=this.____config;const enforceInternalPermission=targetPath=>{const ctx=slothlet.contextManager?.tryGetContext?.();const callerWrapper=ctx?.currentWrapper;if(!callerWrapper)return;const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.enforceAccess){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}const callerPath=callerWrapper.____slothletInternal?.apiPath??"";const callerFilePath=callerWrapper.____slothletInternal?.filePath??null;const runtimeContext=ctx?.context??null;if(!permissionManager.enforceAccess(callerPath,targetPath,callerFilePath,null,runtimeContext)){throw new slothlet.SlothletError("PERMISSION_DENIED",{caller:callerPath,target:targetPath})}};const canTraverseInternalNamespace=targetPath=>{const ctx=slothlet.contextManager?.tryGetContext?.();const callerWrapper=ctx?.currentWrapper;if(!callerWrapper)return false;const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.getRulesForCaller||!permissionManager?.checkAccess){return false}const callerPath=callerWrapper.____slothletInternal?.apiPath??"";const callerFilePath=callerWrapper.____slothletInternal?.filePath??null;const runtimeContext=ctx?.context??null;const callerRules=permissionManager.getRulesForCaller(callerPath);const conditionMatches=condition=>typeof permissionManager.matchesCondition==="function"?permissionManager.matchesCondition(condition,runtimeContext):false;const prefix=`${targetPath}.`;for(const rule of callerRules){if(!rule||rule.effect!=="allow"||typeof rule.target!=="string")continue;const couldMatchDescendant=rule.target.startsWith(prefix)||rule.target==="**"||rule.target==="*";if(!couldMatchDescendant)continue;if(rule.target.startsWith(prefix)&&conditionMatches(rule.condition)){return true}const probePaths=[`${targetPath}.__probe__`];if(rule.target.startsWith(prefix)){const suffix=rule.target.slice(prefix.length);const firstSegment=suffix.split(".")[0];if(firstSegment&&!/[*!?{}]/u.test(firstSegment)){probePaths.unshift(`${targetPath}.${firstSegment}`);probePaths.push(`${targetPath}.${firstSegment}.__probe__`)}}for(const probePath of probePaths){if(permissionManager.checkAccess(callerPath,probePath,callerFilePath,null,runtimeContext,{useCache:false})){return true}}}return false};const createInternalRouteProxy=(value,routePath,seen=new WeakMap)=>{if(!value||typeof value!=="object"&&typeof value!=="function")return value;const isMetaProperty=prop=>prop==="__proto__"||prop==="prototype"||prop==="constructor"||prop==="caller"||prop==="arguments";let routeCache=seen.get(value);if(!routeCache){routeCache=new Map;seen.set(value,routeCache)}if(routeCache.has(routePath)){return routeCache.get(routePath)}const proxy=new Proxy(value,{get(target,prop,receiver){if(typeof prop!=="string"){const result2=Reflect.get(target,prop,receiver);return createInternalRouteProxy(result2,routePath,seen)}const childRoutePath=`${routePath}.${prop}`;let deniedError=null;try{enforceInternalPermission(childRoutePath)}catch(error){deniedError=error}if(deniedError){if(canTraverseInternalNamespace(childRoutePath)){const result2=Reflect.get(target,prop,receiver);if(result2&&(typeof result2==="object"||typeof result2==="function")){return createInternalRouteProxy(result2,childRoutePath,seen)}}throw deniedError}const result=Reflect.get(target,prop,receiver);if(isMetaProperty(prop)){return result}const descriptor=Object.getOwnPropertyDescriptor(target,prop);if(descriptor&&"value"in descriptor&&descriptor.configurable===false&&descriptor.writable===false){return descriptor.value}return createInternalRouteProxy(result,childRoutePath,seen)},getOwnPropertyDescriptor(target,prop){const descriptor=Reflect.getOwnPropertyDescriptor(target,prop);if(!descriptor)return void 0;if(typeof prop!=="string"){return descriptor}const childRoutePath=`${routePath}.${prop}`;if(isMetaProperty(prop)){enforceInternalPermission(childRoutePath);return descriptor}if("get"in descriptor||"set"in descriptor){enforceInternalPermission(childRoutePath);if(descriptor.configurable===true){return{...descriptor,get:typeof descriptor.get==="function"?function slothlet_internal_descriptor_getter(...args){enforceInternalPermission(childRoutePath);return Reflect.apply(descriptor.get,this,args)}:descriptor.get,set:typeof descriptor.set==="function"?function slothlet_internal_descriptor_setter(...args){enforceInternalPermission(childRoutePath);return Reflect.apply(descriptor.set,this,args)}:descriptor.set}}return descriptor}if(!("value"in descriptor)){return descriptor}const descriptorValue=descriptor.value;if(descriptor.configurable===false&&descriptor.writable===false){enforceInternalPermission(childRoutePath);return descriptor}if(!descriptorValue||typeof descriptorValue!=="object"&&typeof descriptorValue!=="function"||typeof descriptorValue==="function"){enforceInternalPermission(childRoutePath);if(typeof descriptorValue==="function"&&descriptor.configurable===true){return{...descriptor,value:createInternalRouteProxy(descriptorValue,childRoutePath,seen)}}return descriptor}return{...descriptor,value:createInternalRouteProxy(descriptorValue,childRoutePath,seen)}},set(target,prop,newValue,receiver){if(typeof prop!=="string"){return Reflect.set(target,prop,newValue,receiver)}const childRoutePath=`${routePath}.${prop}`;enforceInternalPermission(childRoutePath);return Reflect.set(target,prop,newValue,receiver)},defineProperty(target,prop,descriptor){if(typeof prop!=="string"){return Reflect.defineProperty(target,prop,descriptor)}const childRoutePath=`${routePath}.${prop}`;enforceInternalPermission(childRoutePath);return Reflect.defineProperty(target,prop,descriptor)},deleteProperty(target,prop){if(typeof prop!=="string"){return Reflect.deleteProperty(target,prop)}const childRoutePath=`${routePath}.${prop}`;enforceInternalPermission(childRoutePath);return Reflect.deleteProperty(target,prop)},ownKeys(target){return Reflect.ownKeys(target)},apply(target,thisArg,argArray){enforceInternalPermission(routePath);return Reflect.apply(target,thisArg,argArray)},construct(target,argArray,newTarget){enforceInternalPermission(routePath);return Reflect.construct(target,argArray,newTarget)}});routeCache.set(routePath,proxy);return proxy};let version="unknown";try{const pkgPath=new URL("../../../package.json",import.meta.url);const{readFile}=await import("node:fs/promises");const pkgContent=await readFile(pkgPath,"utf-8");const pkg=JSON.parse(pkgContent);version=pkg.version||"unknown"}catch{}const namespace={i18n:{setLanguage,getLanguage,translate,t,initI18n},version,instanceID:slothlet.instanceID,types:TYPE_STATES,api:{add:async function slothlet_api_add(apiPath,folderPath,options={},versionConfig=null){if(!config.api?.mutations?.add){throw new slothlet.SlothletError("INVALID_CONFIG_MUTATIONS_DISABLED",{operation:"api.add",validationError:true})}const{recordHistory:____recordHistory,collisionMode:____collisionMode,mutateExisting:____mutateExisting,...filteredOptions}=options;return slothlet.handlers.apiManager.addApiComponent({apiPath,folderPath,options:filteredOptions,versionConfig:versionConfig||null})},remove:async function slothlet_api_remove(pathOrModuleId){if(!config.api?.mutations?.remove){throw new slothlet.SlothletError("INVALID_CONFIG_MUTATIONS_DISABLED",{operation:"api.remove",validationError:true})}if(typeof pathOrModuleId!=="string"){throw new slothlet.SlothletError("INVALID_ARGUMENT",{argument:"pathOrModuleId",expected:"string",received:typeof pathOrModuleId,validationError:true})}return slothlet.handlers.apiManager.removeApiComponent(pathOrModuleId)},reload:async function slothlet_api_reload(pathOrModuleId,options){if(!config.api?.mutations?.reload){throw new slothlet.SlothletError("INVALID_CONFIG_MUTATIONS_DISABLED",{operation:"api.reload",validationError:true})}if(pathOrModuleId==null||pathOrModuleId===""||pathOrModuleId==="."){return slothlet.handlers.apiManager.reloadApiComponent({apiPath:".",options})}if(typeof pathOrModuleId!=="string"){throw new slothlet.SlothletError("INVALID_ARGUMENT",{argument:"pathOrModuleId",expected:"string, null, undefined, or '.'",received:typeof pathOrModuleId,validationError:true})}const isModuleId=slothlet.handlers.apiManager.state.addHistory.some(entry=>entry.moduleID===pathOrModuleId);if(isModuleId){return slothlet.handlers.apiManager.reloadApiComponent({moduleID:pathOrModuleId,options})}const normalizedPath=slothlet.handlers.apiManager.normalizeApiPath(pathOrModuleId).apiPath;const pathParts=normalizedPath.split(".");let current=slothlet.api;for(const part of pathParts){if(!current||typeof current!=="object"&&typeof current!=="function"){throw new slothlet.SlothletError("INVALID_API_PATH",{apiPath:pathOrModuleId,validationError:true})}current=current[part]}if(current===void 0){throw new slothlet.SlothletError("INVALID_API_PATH",{apiPath:pathOrModuleId,validationError:true})}return slothlet.handlers.apiManager.reloadApiComponent({apiPath:normalizedPath,options})},modules:{discover:function slothlet_modules_discover(options){return slothlet.handlers.moduleManager.discover(options)},sort:function slothlet_modules_sort(results,comparator){return slothlet.handlers.moduleManager.sort(results,comparator)},addModule:async function slothlet_modules_addModule(nameOrResult,options){if(!config.api?.mutations?.add){throw new slothlet.SlothletError("INVALID_CONFIG_MUTATIONS_DISABLED",{operation:"api.modules.addModule",validationError:true})}return slothlet.handlers.moduleManager.addModule(nameOrResult,options)},addModules:async function slothlet_modules_addModules(items,options){if(!config.api?.mutations?.add){throw new slothlet.SlothletError("INVALID_CONFIG_MUTATIONS_DISABLED",{operation:"api.modules.addModules",validationError:true})}return slothlet.handlers.moduleManager.addModules(items,options)},removeModule:async function slothlet_modules_removeModule(name,opts){if(!config.api?.mutations?.remove){throw new slothlet.SlothletError("INVALID_CONFIG_MUTATIONS_DISABLED",{operation:"api.modules.removeModule",validationError:true})}return slothlet.handlers.moduleManager.removeModule(name,opts)},addDiscovered:async function slothlet_modules_addDiscovered(options={}){if(!config.api?.mutations?.add){throw new slothlet.SlothletError("INVALID_CONFIG_MUTATIONS_DISABLED",{operation:"api.modules.addDiscovered",validationError:true})}const{sort:comparator,collisionMode,onFailure,concurrency,...discoverOptions}=options;const found=await slothlet.handlers.moduleManager.discover(discoverOptions);const ordered=slothlet.handlers.moduleManager.sort(found,comparator);return slothlet.handlers.moduleManager.addModules(ordered,{collisionMode,onFailure,concurrency})},getDiscoveryCache:function slothlet_modules_getDiscoveryCache(){return slothlet.handlers.moduleManager.getDiscoveryCache()},clearDiscoveryCache:function slothlet_modules_clearDiscoveryCache(){return slothlet.handlers.moduleManager.clearDiscoveryCache()},getStaleMounts:function slothlet_modules_getStaleMounts(){return slothlet.handlers.moduleManager.getStaleMounts()}}},sanitize:function slothlet_sanitize(str){if(typeof str!=="string"){throw new slothlet.SlothletError("INVALID_ARGUMENT",{argument:"str",expected:"string",received:typeof str,validationError:true})}return slothlet.helpers.sanitize.sanitizePropertyName(str,slothlet.config.sanitize||{})},lockCaller:function slothlet_lockCaller(fn){if(typeof fn!=="function"){throw new slothlet.SlothletError("INVALID_ARGUMENT",{argument:"fn",expected:"function",received:typeof fn,validationError:true})}const capturedWrapper=slothlet.contextManager?.tryGetContext?.()?.currentWrapper??null;if(!capturedWrapper)return fn;const locked=function slothlet_lockedCaller(...args){return slothlet.contextManager.runInContext(slothlet.instanceID,fn,this,args,capturedWrapper,true)};locked._slothletOriginal=fn;return locked},bind:function slothlet_bind(fn){if(typeof fn!=="function"){throw new slothlet.SlothletError("INVALID_ARGUMENT",{argument:"fn",expected:"function",received:typeof fn,validationError:true})}return AsyncResource.bind(fn,"slothlet-bound")},context:{get:key=>{if(slothlet.contextManager.constructor.name==="LiveContextManager"){const currentID=slothlet.contextManager.currentInstanceID;if(currentID){const activeStore=slothlet.contextManager.instances.get(currentID);const isOurInstance=currentID===slothlet.instanceID||currentID?.startsWith(slothlet.instanceID+"__run_")||activeStore?.parentInstanceID===slothlet.instanceID;if(isOurInstance&&activeStore){return key?activeStore.context[key]:{...activeStore.context}}}const store=slothlet.contextManager.instances.get(slothlet.instanceID);if(!store){const baseContext2=slothlet.context||{};return key?baseContext2[key]:{...baseContext2}}return key?store.context[key]:{...store.context}}if(slothlet.contextManager.constructor.name==="AsyncContextManager"){let currentStore=slothlet.contextManager.tryGetContext();if(!currentStore){const baseStore2=slothlet.contextManager.instances.get(slothlet.instanceID);const baseContext3=baseStore2?.context||{};return key?baseContext3[key]:{...baseContext3}}const isOurInstance=currentStore.instanceID===slothlet.instanceID||currentStore.parentInstanceID===slothlet.instanceID||currentStore.instanceID.startsWith(slothlet.instanceID+"__run_");if(isOurInstance){return key?currentStore.context[key]:{...currentStore.context}}const baseStore=slothlet.contextManager.instances.get(slothlet.instanceID);const baseContext2=baseStore?.context||{};return key?baseContext2[key]:{...baseContext2}}const baseContext=slothlet.context||{};return key?baseContext[key]:{...baseContext}},diagnostics:()=>{if(!slothlet.config?.diagnostics)return void 0;const managerType=slothlet.contextManager.constructor.name;const result={instanceID:slothlet.instanceID,managerType,instancesMapSize:slothlet.contextManager.instances.size,instancesMapKeys:Array.from(slothlet.contextManager.instances.keys()),baseContext:slothlet.context};const store=slothlet.contextManager.instances.get(slothlet.instanceID);result.storeFromInstancesMap=store?{instanceID:store.instanceID,context:store.context,createdAt:store.createdAt}:null;if(managerType==="AsyncContextManager"){try{const currentCtx=slothlet.contextManager.tryGetContext();result.currentALSContext=currentCtx?{instanceID:currentCtx.instanceID,context:currentCtx.context,hasParent:!!currentCtx.parentContext,parentInstanceID:currentCtx.parentInstanceID}:null}catch(____error){result.currentALSContext=null}}if(managerType==="LiveContextManager"){result.currentInstanceID=slothlet.contextManager.currentInstanceID}return result},run:this.createRunFunction(),scope:this.createScopeFunction()},hook:{on:function slothlet_hook_on(typePattern,handler,options={}){if(!slothlet.handlers?.hookManager){throw new slothlet.SlothletError("HOOKS_NOT_INITIALIZED",{validationError:true})}return slothlet.handlers.hookManager.on(typePattern,handler,options)},remove:function slothlet_hook_remove(filter={}){if(!slothlet.handlers?.hookManager){throw new slothlet.SlothletError("HOOKS_NOT_INITIALIZED",{validationError:true})}return slothlet.handlers.hookManager.remove(filter)},clear:function slothlet_hook_clear(filter={}){return this.remove(filter)},off:function slothlet_hook_off(idOrFilter){if(!slothlet.handlers?.hookManager){throw new slothlet.SlothletError("HOOKS_NOT_INITIALIZED",{validationError:true})}const filter=typeof idOrFilter==="string"?{id:idOrFilter}:idOrFilter;return slothlet.handlers.hookManager.remove(filter)},enable:function slothlet_hook_enable(filter={}){if(!slothlet.handlers?.hookManager){throw new slothlet.SlothletError("HOOKS_NOT_INITIALIZED",{validationError:true})}return slothlet.handlers.hookManager.enable(filter)},disable:function slothlet_hook_disable(filter={}){if(!slothlet.handlers?.hookManager){throw new slothlet.SlothletError("HOOKS_NOT_INITIALIZED",{validationError:true})}return slothlet.handlers.hookManager.disable(filter)},list:function slothlet_hook_list(filter={}){if(!slothlet.handlers?.hookManager){throw new slothlet.SlothletError("HOOKS_NOT_INITIALIZED",{validationError:true})}return slothlet.handlers.hookManager.list(filter)}},metadata:{setGlobal:function slothlet_metadata_setGlobal(keyOrObj,value){if(!slothlet.handlers?.metadata){throw new slothlet.SlothletError("METADATA_NOT_AVAILABLE",{handlersKeys:slothlet.handlers?Object.keys(slothlet.handlers).join(", "):"undefined",validationError:true})}const validateGlobalMetadataKeyPath=keyPath=>{const blocked=new Set(["__proto__","prototype","constructor"]);if(typeof keyPath!=="string"||keyPath.length===0){throw new slothlet.SlothletError("INVALID_METADATA_KEY",{key:keyPath,type:typeof keyPath,expected:"non-empty string"})}const segments=keyPath.split(".");for(const segment of segments){if(!segment||blocked.has(segment)){throw new slothlet.SlothletError("INVALID_METADATA_KEY",{key:keyPath,type:typeof keyPath,expected:"safe dot-notation key without reserved segments"})}}};const normalizeGlobalMetadataObject=(source,prefix="",ancestors=new WeakSet)=>{if(ancestors.has(source)){throw new slothlet.SlothletError("INVALID_ARGUMENT",{argument:"keyOrObj",expected:"acyclic object",received:"circular reference",validationError:true})}ancestors.add(source);try{const normalized={};for(const[key,nestedValue]of Object.entries(source)){const fullKey=prefix?`${prefix}.${key}`:key;validateGlobalMetadataKeyPath(fullKey);const nestedProto=nestedValue&&typeof nestedValue==="object"?Object.getPrototypeOf(nestedValue):null;const isPlainNested=nestedProto===Object.prototype||nestedProto===null;if(nestedValue&&typeof nestedValue==="object"&&!Array.isArray(nestedValue)&&isPlainNested){normalized[key]=normalizeGlobalMetadataObject(nestedValue,fullKey,ancestors);continue}normalized[key]=nestedValue}return normalized}finally{ancestors.delete(source)}};if(keyOrObj&&typeof keyOrObj==="object"&&!Array.isArray(keyOrObj)){const normalizedMetadata=normalizeGlobalMetadataObject(keyOrObj);for(const[key,nestedValue]of Object.entries(normalizedMetadata)){slothlet.handlers.metadata.setGlobalMetadata(key,nestedValue)}return}if(typeof keyOrObj!=="string"){throw new slothlet.SlothletError("INVALID_ARGUMENT",{argument:"keyOrObj",expected:"string or object",received:typeof keyOrObj,validationError:true})}validateGlobalMetadataKeyPath(keyOrObj);return slothlet.handlers.metadata.setGlobalMetadata(keyOrObj,value)},set:function slothlet_metadata_set(fn,key,value){if(!slothlet.handlers?.metadata){throw new slothlet.SlothletError("METADATA_NOT_AVAILABLE",{handlersKeys:slothlet.handlers?Object.keys(slothlet.handlers).join(", "):"undefined",validationError:true})}return slothlet.handlers.metadata.setUserMetadata(fn,key,value)},remove:function slothlet_metadata_remove(fn,key){return slothlet.handlers.metadata.removeUserMetadata(fn,key)},setFor:function slothlet_metadata_setFor(pathOrModuleId,keyOrObj,value){if(!slothlet.handlers?.metadata){throw new slothlet.SlothletError("METADATA_NOT_AVAILABLE",{handlersKeys:slothlet.handlers?Object.keys(slothlet.handlers).join(", "):"undefined",validationError:true})}const resolvedPath=_resolvePathOrModuleId(slothlet,pathOrModuleId);return slothlet.handlers.metadata.setPathMetadata(resolvedPath,keyOrObj,value)},removeFor:function slothlet_metadata_removeFor(pathOrModuleId,key){if(!slothlet.handlers?.metadata)return;const resolvedPath=_resolvePathOrModuleId(slothlet,pathOrModuleId);return slothlet.handlers.metadata.removePathMetadata(resolvedPath,key)},getFor:function slothlet_metadata_getFor(pathOrModuleId){if(!slothlet.handlers?.metadata)return{};const resolvedPath=_resolvePathOrModuleId(slothlet,pathOrModuleId);return slothlet.handlers.metadata.getPathMetadata(resolvedPath)},setForVersion:function slothlet_metadata_setForVersion(logicalPath,versionTag,keyOrObj,value){if(!slothlet.handlers?.metadata){throw new slothlet.SlothletError("METADATA_NOT_AVAILABLE",{handlersKeys:slothlet.handlers?Object.keys(slothlet.handlers).join(", "):"undefined",validationError:true})}const info=slothlet.handlers?.versionManager?.list(logicalPath);if(!info||!info.versions?.[versionTag]){throw new slothlet.SlothletError("VERSION_NOT_FOUND",{version:versionTag,apiPath:logicalPath})}const{moduleID}=info.versions[versionTag];const resolvedPath=_resolvePathOrModuleId(slothlet,moduleID);return slothlet.handlers.metadata.setPathMetadata(resolvedPath,keyOrObj,value)},getForVersion:function slothlet_metadata_getForVersion(logicalPath,versionTag){if(!slothlet.handlers?.metadata)return{};const info=slothlet.handlers?.versionManager?.list(logicalPath);if(!info||!info.versions?.[versionTag])return{};const{moduleID}=info.versions[versionTag];const resolvedPath=_resolvePathOrModuleId(slothlet,moduleID);return slothlet.handlers.metadata.getPathMetadata(resolvedPath)}},scope:this.createScopeFunction(),run:this.createRunFunction(),reload:async(options={})=>{if(!config.api?.mutations?.reload){throw new slothlet.SlothletError("INVALID_CONFIG_MUTATIONS_DISABLED",{operation:"reload",validationError:true})}return slothlet.reload(options)},shutdown:async()=>{return slothlet.shutdown()},owner:{get:apiPath=>{if(slothlet.handlers?.ownership){return slothlet.handlers.ownership.getPathOwnership(apiPath)}return null}},materialize:(()=>{const mgr=slothlet.handlers?.materialize;const getMaterializedState=()=>{enforceInternalPermission("slothlet.materialize.materialized");return mgr?.materialized??false};const getMaterializeStats=()=>{enforceInternalPermission("slothlet.materialize.get");return mgr?mgr.get():{total:0,materialized:0,remaining:0,percentage:100}};const waitForMaterialization=async()=>{enforceInternalPermission("slothlet.materialize.wait");if(!mgr)return;return mgr.wait()};if(!mgr){return Object.freeze({get materialized(){return getMaterializedState()},get:getMaterializeStats,wait:waitForMaterialization})}return Object.freeze({get materialized(){return getMaterializedState()},get:getMaterializeStats,wait:waitForMaterialization})})(),lifecycle:(()=>{const handler=slothlet.handlers?.lifecycle;const noop=()=>{};if(!handler)return{on:noop,off:noop};return{on:handler.on.bind(handler),off:handler.off.bind(handler)}})(),env:slothlet.envSnapshot,versioning:{list:function slothlet_version_list(logicalPath){if(!slothlet.handlers?.versionManager)return void 0;return slothlet.handlers.versionManager.list(logicalPath)},setDefault:function slothlet_version_setDefault(logicalPath,versionTag){if(!slothlet.handlers?.versionManager)return;return slothlet.handlers.versionManager.setDefault(logicalPath,versionTag)},unregister:async function slothlet_version_unregister(logicalPath,versionTag){if(!slothlet.handlers?.versionManager)return false;const info=slothlet.handlers.versionManager.list(logicalPath);if(!info||!info.versions?.[versionTag])return false;const{moduleID:versionedModuleID}=info.versions[versionTag];await slothlet.handlers.apiManager.removeApiComponent(versionedModuleID);return true},getVersionMetadata:function slothlet_version_getVersionMetadata(logicalPath,versionTag){if(!slothlet.handlers?.versionManager)return void 0;return slothlet.handlers.versionManager.getVersionMetadataByPath(logicalPath,versionTag)},setVersionMetadata:function slothlet_version_setVersionMetadata(logicalPath,versionTag,patch){if(!slothlet.handlers?.versionManager)return;return slothlet.handlers.versionManager.setVersionMetadataByPath(logicalPath,versionTag,patch)}},permissions:{addRule:function slothlet_permissions_addRule(rule){if(!config.api?.mutations?.permissions){throw new slothlet.SlothletError("INVALID_CONFIG_MUTATIONS_DISABLED",{operation:"api.slothlet.permissions.addRule",validationError:true})}const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.addRule){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}const ruleId=permissionManager.addRule(rule,null);if(slothlet.handlers?.apiManager?.state?.operationHistory){slothlet.handlers.apiManager.state.operationHistory.push({type:"addPermissionRule",rule,ownerModuleID:null,ruleId,timestamp:Date.now()})}return ruleId},removeRule:function slothlet_permissions_removeRule(ruleId){if(!config.api?.mutations?.permissions){throw new slothlet.SlothletError("INVALID_CONFIG_MUTATIONS_DISABLED",{operation:"api.slothlet.permissions.removeRule",validationError:true})}const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.removeRule){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}const ctx=slothlet.contextManager?.tryGetContext?.();const currentWrapper=ctx?.currentWrapper;const callerModuleID=currentWrapper?.____slothletInternal?.moduleID??null;const result=slothlet.handlers.permissionManager.removeRule(ruleId,callerModuleID);if(result&&slothlet.handlers?.apiManager?.state?.operationHistory){slothlet.handlers.apiManager.state.operationHistory.push({type:"removePermissionRule",ruleId,callerModuleID,timestamp:Date.now()})}return result},self:{access:function slothlet_permissions_self_access(target){const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.checkAccess){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}const ctx=slothlet.contextManager?.tryGetContext?.();const currentWrapper=ctx?.currentWrapper;const callerPath=currentWrapper?.____slothletInternal?.apiPath??"";const callerFilePath=currentWrapper?.____slothletInternal?.filePath??null;const runtimeContext=ctx?.context??null;return slothlet.handlers.permissionManager.checkAccess(callerPath,target,callerFilePath,null,runtimeContext)},rules:function slothlet_permissions_self_rules(){const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.getRulesForCaller){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}const currentWrapper=slothlet.contextManager?.tryGetContext?.()?.currentWrapper;const callerPath=currentWrapper?.____slothletInternal?.apiPath??"";return slothlet.handlers.permissionManager.getRulesForCaller(callerPath)}},global:{checkAccess:function slothlet_permissions_global_checkAccess(caller,target){const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.checkAccess){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}const runtimeContext=slothlet.contextManager?.tryGetContext?.()?.context??null;return slothlet.handlers.permissionManager.checkAccess(caller,target,null,null,runtimeContext)},rulesForPath:function slothlet_permissions_global_rulesForPath(path){const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.getRulesForPath){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}return slothlet.handlers.permissionManager.getRulesForPath(path)},rulesByModule:function slothlet_permissions_global_rulesByModule(moduleID){const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.getRulesByModule){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}return slothlet.handlers.permissionManager.getRulesByModule(moduleID)}},control:{get enabled(){const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.isEnabled){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}return permissionManager.isEnabled()},enable:function slothlet_permissions_control_enable(){const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.enable){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}slothlet.handlers.permissionManager.enable()},disable:function slothlet_permissions_control_disable(){const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.disable){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}slothlet.handlers.permissionManager.disable()},get readGatingEnabled(){const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.isReadGatingEnabled){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}return permissionManager.isReadGatingEnabled()},readGating:function slothlet_permissions_control_readGating(value){const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.setReadGating){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}slothlet.handlers.permissionManager.setReadGating(value)}}}};if(!config.hook?.enabled&&config.diagnostics!==true){delete namespace.hooks}if(config.diagnostics===true){namespace.diag={describe:(showAll=false)=>{if(showAll){return{...userApi}}return Reflect.ownKeys(userApi)},reference:slothlet.reference||null,context:slothlet.context||{},inspect:()=>{return slothlet.getDiagnostics()},getAPI:()=>{return slothlet.getAPI()},getOwnership:()=>{return slothlet.getOwnership()},owner:{get:apiPath=>{if(slothlet.handlers?.ownership){return slothlet.handlers.ownership.getPathOwnership(apiPath)}return null}},caches:{get:()=>{if(slothlet.handlers?.apiCacheManager){return slothlet.handlers.apiCacheManager.getCacheDiagnostics()}return{totalCaches:0,caches:[]}},getAllModuleIDs:()=>{if(slothlet.handlers?.apiCacheManager){return slothlet.handlers.apiCacheManager.getAllModuleIDs()}return[]},has:moduleID=>{if(slothlet.handlers?.apiCacheManager){return slothlet.handlers.apiCacheManager.has(moduleID)}return false}},SlothletWarning:slothlet.SlothletWarning,hook:slothlet.handlers?.hookManager?{get enabled(){return slothlet.handlers.hookManager.enabled},compilePattern:pattern=>{return slothlet.handlers.hookManager.getCompilePatternForDiagnostics()(pattern)}}:void 0}}return createInternalRouteProxy(namespace,"slothlet")}createShutdownFunction(){const slothlet=this.slothlet;const shutdownFunction={shutdown:async()=>{if(slothlet.userHooks?.shutdown&&typeof slothlet.userHooks.shutdown==="function"){await slothlet.userHooks.shutdown()}return slothlet.shutdown()}}.shutdown;return shutdownFunction}createRunFunction(){const slothlet=this.slothlet;const scopeFunc=this.createScopeFunction();const runFunction={run:async(contextData,callback,...args)=>{if(slothlet.config.scope===false){throw new slothlet.SlothletError("SCOPE_DISABLED",{},null,{validationError:true})}if(!contextData||typeof contextData!=="object"){throw new slothlet.SlothletError("SCOPE_INVALID_CONTEXT",{received:typeof contextData},null,{validationError:true})}if(typeof callback!=="function"){throw new slothlet.SlothletError("SCOPE_INVALID_CALLBACK",{received:typeof callback},null,{validationError:true})}return scopeFunc({context:contextData,fn:callback,args,merge:slothlet.config.scope?.merge||"shallow",isolation:slothlet.config.scope?.isolation||"partial"})}}.run;return runFunction}createScopeFunction(){const slothlet=this.slothlet;const scopeFunction={scope:async options=>{if(slothlet.config.scope===false){throw new slothlet.SlothletError("SCOPE_DISABLED",{},null,{validationError:true})}if(!options||typeof options!=="object"){throw new slothlet.SlothletError("SCOPE_INVALID_OPTIONS",{received:typeof options},null,{validationError:true})}if(!options.fn||typeof options.fn!=="function"){throw new slothlet.SlothletError("SCOPE_INVALID_FN",{received:typeof options?.fn},null,{validationError:true})}if(!options.context||typeof options.context!=="object"){throw new slothlet.SlothletError("SCOPE_INVALID_CONTEXT_OBJECT",{received:typeof options?.context},null,{validationError:true})}const{context:contextData,fn,args=[],merge="shallow",isolation}=options;if(merge!=="shallow"&&merge!=="deep"){throw new slothlet.SlothletError("SCOPE_INVALID_MERGE_STRATEGY",{merge},null,{validationError:true})}const isolationMode=isolation||slothlet.config.scope?.isolation||"partial";if(isolationMode!=="partial"&&isolationMode!=="full"){throw new slothlet.SlothletError("SCOPE_INVALID_ISOLATION_MODE",{isolationMode},null,{validationError:true})}const contextManager=slothlet.contextManager;if(!contextManager){throw new slothlet.SlothletError("NO_CONTEXT_MANAGER",{validationError:true})}const{utilities}=slothlet.helpers;if(contextManager.constructor.name==="LiveContextManager"){let currentStore=null;const currentID=contextManager.currentInstanceID;if(currentID){const activeStore=contextManager.instances.get(currentID);const isOurContext=currentID===slothlet.instanceID||activeStore?.parentInstanceID===slothlet.instanceID||currentID.startsWith(slothlet.instanceID+"__run_");if(isOurContext){currentStore=activeStore}}if(!currentStore){currentStore=contextManager.instances.get(slothlet.instanceID)}if(!currentStore){throw new slothlet.SlothletError("CONTEXT_NOT_FOUND",{instanceID:slothlet.instanceID,availableInstances:[...contextManager.instances.keys()].join(", ")||"none",validationError:true})}let mergedContext;if(merge==="deep"){mergedContext=utilities.deepMerge(currentStore.context,contextData);mergedContext=structuredClone(mergedContext)}else{const clonedParent=structuredClone(currentStore.context);mergedContext={...clonedParent,...contextData}}const childInstanceID=`${slothlet.instanceID}__run_${Date.now()}_${Math.random().toString(36).slice(2,9)}`;const childStore={instanceID:childInstanceID,context:mergedContext,self:isolationMode==="full"?makeCopyOnWriteSelf(currentStore.self):currentStore.self,config:currentStore.config,createdAt:currentStore.createdAt,parentInstanceID:slothlet.instanceID,currentWrapper:currentStore.currentWrapper,callerWrapper:currentStore.callerWrapper,slothlet:currentStore.slothlet};contextManager.instances.set(childInstanceID,childStore);const previousInstanceID=contextManager.currentInstanceID;try{contextManager.currentInstanceID=childInstanceID;return await fn(...args)}finally{contextManager.currentInstanceID=previousInstanceID;contextManager.instances.delete(childInstanceID)}}if(contextManager.constructor.name==="AsyncContextManager"){let currentStore=null;const activeStore=contextManager.tryGetContext();if(activeStore){const isOurContext=activeStore.instanceID===slothlet.instanceID||activeStore.parentInstanceID===slothlet.instanceID||activeStore.instanceID.startsWith(slothlet.instanceID+"__run_");if(isOurContext){currentStore=activeStore}}if(!currentStore){const baseStore=contextManager.instances.get(slothlet.instanceID);if(!baseStore){throw new slothlet.SlothletError("CONTEXT_NOT_FOUND",{instanceID:slothlet.instanceID,availableInstances:[...contextManager.instances.keys()].join(", ")||"none",validationError:true})}currentStore=baseStore}let mergedContext;if(merge==="deep"){mergedContext=utilities.deepMerge(currentStore.context,contextData);mergedContext=structuredClone(mergedContext)}else{const clonedParent=structuredClone(currentStore.context);mergedContext={...clonedParent,...contextData}}const childInstanceID=`${slothlet.instanceID}__run_${Date.now()}_${Math.random().toString(36).slice(2,9)}`;const childStore={instanceID:childInstanceID,context:mergedContext,self:isolationMode==="full"?makeCopyOnWriteSelf(currentStore.self):currentStore.self,config:currentStore.config,createdAt:currentStore.createdAt,parentInstanceID:slothlet.instanceID,currentWrapper:currentStore.currentWrapper,callerWrapper:currentStore.callerWrapper,slothlet:currentStore.slothlet};contextManager.instances.set(childInstanceID,childStore);try{return await contextManager.als.run(childStore,async()=>{return await fn(...args)})}finally{contextManager.instances.delete(childInstanceID)}}throw new slothlet.SlothletError("UNSUPPORTED_CONTEXT_MANAGER",{manager:contextManager.constructor.name,validationError:true})}}.scope;return scopeFunction}createDestroyFunction(api){const slothlet=this.slothlet;const destroyFunction={destroy:async()=>{if(slothlet.userHooks?.destroy&&typeof slothlet.userHooks.destroy==="function"){await slothlet.userHooks.destroy()}if(api&&typeof api.shutdown==="function"){await api.shutdown()}else{await slothlet.shutdown()}slothlet.isDestroyed=true;const objectsToClear=[api,slothlet.api].filter(obj=>obj&&typeof obj==="object");for(const obj of objectsToClear){const keys=Object.keys(obj);for(const key of keys){try{delete obj[key]}catch(_){}}}slothlet.api=null}}.destroy;return destroyFunction}attachBuiltins(userApi,builtins){Object.defineProperty(userApi,"slothlet",{value:builtins.slothlet,enumerable:true,writable:false,configurable:true});Object.defineProperty(userApi,"shutdown",{value:builtins.shutdown,enumerable:true,writable:false,configurable:true});if(builtins.destroy!==null){Object.defineProperty(userApi,"destroy",{value:builtins.destroy,enumerable:true,writable:false,configurable:true})}}}export{ApiBuilder};
@@ -0,0 +1,17 @@
1
+ /*
2
+ Copyright 2026 CLDMV/Shinrai
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import{ComponentBase}from"@cldmv/slothlet/factories/component-base";import{SlothletError}from"@cldmv/slothlet/errors";import{discoverModules}from"@cldmv/slothlet/helpers/module-discovery";import{sortModules}from"@cldmv/slothlet/helpers/module-sort";const DEFAULT_MODULE_COLLISION_MODE="merge";class ModuleManager extends ComponentBase{static slothletProperty="moduleManager";#cache=new Map;#mounted=new Map;constructor(slothlet){super(slothlet)}async discover(options={}){await this.#emit("modules:discover-start",{scanRoot:options.scanRoot,options});const found=await discoverModules(options);this.#cache.clear();for(const result of found){const key=`${result.packageName}@${result.manifest.version}`;this.#cache.set(key,result)}await this.#emit("modules:discover-complete",{found,stale:this.getStaleMounts()});return found}sort(results,comparator){return sortModules(results,comparator)}getDiscoveryCache(){return[...this.#cache.values()]}clearDiscoveryCache(){this.#cache.clear()}getStaleMounts(){const stale=[];for(const[key,mountResult]of this.#mounted){if(!this.#cache.has(key)){stale.push(mountResult)}}return stale}async addModule(nameOrResult,options={}){const collisionMode=options.collisionMode??DEFAULT_MODULE_COLLISION_MODE;const discoverResult=await this.#resolveToDiscoverResult(nameOrResult,options);await this.#emit("modules:mount-start",{items:[nameOrResult],options});const mountResult=await this.#mountSingle(discoverResult,collisionMode,null);await this.#emit("modules:loaded",{mounted:[mountResult]});return mountResult}async addModules(items,options={}){if(!Array.isArray(items)){throw new SlothletError("INVALID_ARGUMENT",{argument:"items",expected:"array of string|DiscoverResult",received:typeof items},null,{validationError:true})}const onFailure=options.onFailure??"throw";const concurrency=Math.max(1,Number(options.concurrency)||1);const collisionMode=options.collisionMode??DEFAULT_MODULE_COLLISION_MODE;if(!["throw","rollback","best-effort"].includes(onFailure)){throw new SlothletError("INVALID_ARGUMENT",{argument:"onFailure",expected:"throw|rollback|best-effort",received:String(onFailure)},null,{validationError:true})}const resolved=[];for(const item of items){resolved.push(await this.#resolveToDiscoverResult(item,options))}const versionConfigs=this.#buildVersionConfigs(resolved);await this.#emit("modules:mount-start",{items,options});let outcome;if(concurrency===1){outcome=await this.#mountSerial(resolved,collisionMode,onFailure,versionConfigs)}else{outcome=await this.#mountParallel(resolved,collisionMode,onFailure,concurrency,versionConfigs)}const loadedPayload=Array.isArray(outcome)?{mounted:outcome}:{mounted:outcome.mounted,failed:outcome.failed};await this.#emit("modules:loaded",loadedPayload);return outcome}async removeModule(name,opts={}){const matches=[];for(const[key,mountResult]of this.#mounted){if(mountResult.packageName!==name)continue;if(opts.version!==void 0&&mountResult.discoverResult.manifest.version!==opts.version)continue;matches.push({key,mountResult})}if(matches.length===0)return false;for(const{key,mountResult}of matches){await this.slothlet.handlers.apiManager.removeApiComponent(mountResult.moduleID);this.#mounted.delete(key)}return true}async#resolveToDiscoverResult(arg,options){if(arg&&typeof arg==="object"&&typeof arg.packageName==="string"){return arg}if(typeof arg!=="string"){throw new SlothletError("INVALID_ARGUMENT",{argument:"module name or DiscoverResult",expected:"string or DiscoverResult object",received:typeof arg},null,{validationError:true})}if(this.#cache.size===0){await this.discover(options.discover??{})}const candidates=[];for(const result of this.#cache.values()){if(result.packageName!==arg)continue;if(options.version!==void 0&&result.manifest.version!==options.version)continue;candidates.push(result)}if(candidates.length===0){throw new SlothletError("MODULE_PACKAGE_NOT_FOUND",{packageName:arg,hint:"addModule(name) requires the package to be installed under one of the scanned roots. Run discover() first, or pass a DiscoverResult object directly."},null,{validationError:true})}if(candidates.length>1){throw new SlothletError("INVALID_ARGUMENT",{argument:"version",expected:"version disambiguator (multi-version cache hit)",received:"undefined"},null,{validationError:true})}return candidates[0]}async#mountSingle(discoverResult,collisionMode,versionConfig){const mountPathDotted=discoverResult.mountPath.join(".");const effectiveMountPath=versionConfig?.version?`${versionConfig.version}.${mountPathDotted}`:mountPathDotted;if(collisionMode==="error"){const existing=this.#findExactMountAt(effectiveMountPath);if(existing){throw new SlothletError("MODULE_MOUNT_COLLISION",{packageName:discoverResult.packageName,mountPath:effectiveMountPath,existingModuleID:existing.moduleID,collisionMode},null,{validationError:true})}}const version=discoverResult.manifest.version;const underlyingCollisionMode=collisionMode==="error"?"merge":collisionMode;const moduleID=await this.slothlet.handlers.apiManager.addApiComponent({apiPath:mountPathDotted,folderPath:discoverResult.apiDir,options:{collisionMode:underlyingCollisionMode,metadata:{_module:{manifest:discoverResult.manifest}}},versionConfig:versionConfig??null});const result={packageName:discoverResult.packageName,mountPath:effectiveMountPath,moduleID,discoverResult,versionConfig:versionConfig??null};this.#mounted.set(`${discoverResult.packageName}@${version}`,result);await this.#emit("modules:mount-complete",{name:discoverResult.packageName,version,mountPath:effectiveMountPath,moduleID});return result}#buildVersionConfigs(resolved){const byName=new Map;for(const r of resolved){const list=byName.get(r.packageName)??[];list.push(r);byName.set(r.packageName,list)}const highestByName=new Map;for(const[name,group]of byName){if(group.length<2)continue;const versions=group.map(r=>r.manifest.version);highestByName.set(name,pickHighestSemver(versions))}const configs=new Map;for(let i=0;i<resolved.length;i++){const r=resolved[i];const group=byName.get(r.packageName);if(!group||group.length<2){configs.set(i,null);continue}const versionTag=semverToTag(r.manifest.version);const isDefault=r.manifest.version===highestByName.get(r.packageName);configs.set(i,{version:versionTag,default:isDefault})}return configs}async#emit(event,payload){const lifecycle=this.slothlet?.handlers?.lifecycle;if(lifecycle&&typeof lifecycle.emit==="function"){await lifecycle.emit(event,payload)}}#findExactMountAt(dottedPath){const history=this.slothlet?.handlers?.apiManager?.state?.addHistory;if(!Array.isArray(history))return null;for(let i=history.length-1;i>=0;i--){const entry=history[i];if(entry?.apiPath===dottedPath)return entry}return null}async#mountSerial(resolved,collisionMode,onFailure,versionConfigs){const mounted=[];const failed=[];for(let i=0;i<resolved.length;i++){const item=resolved[i];try{const mountResult=await this.#mountSingle(item,collisionMode,versionConfigs?.get(i)??null);mounted.push(mountResult)}catch(err){if(onFailure==="throw"){throw err}if(onFailure==="rollback"){await this.#rollback(mounted);throw err}failed.push({item,error:err})}}if(onFailure==="best-effort"){return{mounted,failed}}return mounted}async#mountParallel(resolved,collisionMode,onFailure,concurrency,versionConfigs){const mounted=[];const failed=[];let firstError=null;let nextIndex=0;const worker=async()=>{while(true){if(firstError&&onFailure!=="best-effort")return;const i=nextIndex++;if(i>=resolved.length)return;const item=resolved[i];try{const mountResult=await this.#mountSingle(item,collisionMode,versionConfigs?.get(i)??null);mounted.push(mountResult)}catch(err){if(onFailure==="best-effort"){failed.push({item,error:err})}else if(!firstError){firstError=err}}}};const workers=Array.from({length:Math.min(concurrency,resolved.length)},()=>worker());await Promise.all(workers);if(firstError){if(onFailure==="rollback"){await this.#rollback(mounted)}throw firstError}if(onFailure==="best-effort"){return{mounted,failed}}return mounted}async#rollback(mounted){for(const m of mounted){try{await this.slothlet.handlers.apiManager.removeApiComponent(m.moduleID);this.#mounted.delete(`${m.packageName}@${m.discoverResult.manifest.version}`)}catch{}}}}function semverToTag(version){const m=/^(\d+)/.exec(String(version));return m?`v${m[1]}`:`v${version}`}function pickHighestSemver(versions){if(versions.length===1)return versions[0];const segs=v=>String(v).split(/[.\-+]/).map(p=>{const n=Number.parseInt(p,10);return Number.isFinite(n)?n:0});let best=versions[0];let bestSegs=segs(best);for(let i=1;i<versions.length;i++){const candidate=versions[i];const candidateSegs=segs(candidate);const len=Math.max(candidateSegs.length,bestSegs.length);let candidateWins=false;for(let j=0;j<len;j++){const a=candidateSegs[j]??0;const b=bestSegs[j]??0;if(a>b){candidateWins=true;break}if(a<b)break}if(candidateWins){best=candidate;bestSegs=candidateSegs}}return best}export{ModuleManager};
@@ -14,4 +14,4 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import{EventEmitter}from"node:events";import{AsyncResource}from"node:async_hooks";let isInApiContext=null;function setApiContextChecker(checker){isInApiContext=checker}const originalMethods=new Map;const wrappedListeners=new Map;const trackedEmitters=new Set;let isPatchingEnabled=false;function runtime_wrapEventListener(listener){const resource=new AsyncResource("slothlet-event-listener");const runtime_wrappedListener=function(...args){return resource.runInAsyncScope(()=>{return listener.apply(this,args)},this)};runtime_wrappedListener._slothletOriginal=listener;runtime_wrappedListener._slothletResource=resource;return runtime_wrappedListener}function runtime_getListenerTracking(emitter){let emitterTracking=wrappedListeners.get(emitter);if(!emitterTracking){emitterTracking=new Map;wrappedListeners.set(emitter,emitterTracking)}return emitterTracking}function runtime_trackListener(emitter,event,originalListener,wrappedListener){const emitterTracking=runtime_getListenerTracking(emitter);let eventTracking=emitterTracking.get(event);if(!eventTracking){eventTracking=new Map;emitterTracking.set(event,eventTracking)}eventTracking.set(originalListener,wrappedListener)}function runtime_getWrappedListener(emitter,event,originalListener){const emitterTracking=wrappedListeners.get(emitter);if(!emitterTracking)return void 0;const eventTracking=emitterTracking.get(event);if(!eventTracking)return void 0;return eventTracking.get(originalListener)}function runtime_untrackListener(emitter,event,originalListener){const emitterTracking=wrappedListeners.get(emitter);if(!emitterTracking)return;const eventTracking=emitterTracking.get(event);if(!eventTracking)return;const wrappedListener=eventTracking.get(originalListener);if(wrappedListener){wrappedListener._slothletResource=null;eventTracking.delete(originalListener)}if(eventTracking.size===0){emitterTracking.delete(event)}if(emitterTracking.size===0){wrappedListeners.delete(emitter)}}function runtime_shouldWrapListener(listener){if(typeof listener!=="function")return false;if(listener._slothletOriginal)return false;return true}function runtime_maybeTrackEmitter(emitter){if(isInApiContext&&isInApiContext()){trackedEmitters.add(emitter)}}function runtime_patchOn(){const original=EventEmitter.prototype.on;originalMethods.set("on",original);EventEmitter.prototype.on=function(event,listener){runtime_maybeTrackEmitter(this);if(!runtime_shouldWrapListener(listener)){return original.call(this,event,listener)}const wrapped=runtime_wrapEventListener(listener);runtime_trackListener(this,event,listener,wrapped);return original.call(this,event,wrapped)};EventEmitter.prototype.addListener=EventEmitter.prototype.on}function runtime_patchOnce(){const original=EventEmitter.prototype.once;originalMethods.set("once",original);EventEmitter.prototype.once=function(event,listener){runtime_maybeTrackEmitter(this);if(!runtime_shouldWrapListener(listener)){return original.call(this,event,listener)}const wrapped=runtime_wrapEventListener(listener);const runtime_onceWrapper=function(...args){const result=wrapped.apply(this,args);runtime_untrackListener(this,event,listener);return result};runtime_onceWrapper._slothletOriginal=listener;runtime_onceWrapper._slothletResource=wrapped._slothletResource;runtime_trackListener(this,event,listener,runtime_onceWrapper);return original.call(this,event,runtime_onceWrapper)}}function runtime_patchPrependListener(){const original=EventEmitter.prototype.prependListener;originalMethods.set("prependListener",original);EventEmitter.prototype.prependListener=function(event,listener){runtime_maybeTrackEmitter(this);if(!runtime_shouldWrapListener(listener)){return original.call(this,event,listener)}const wrapped=runtime_wrapEventListener(listener);runtime_trackListener(this,event,listener,wrapped);return original.call(this,event,wrapped)}}function runtime_patchPrependOnceListener(){const original=EventEmitter.prototype.prependOnceListener;originalMethods.set("prependOnceListener",original);EventEmitter.prototype.prependOnceListener=function(event,listener){runtime_maybeTrackEmitter(this);if(!runtime_shouldWrapListener(listener)){return original.call(this,event,listener)}const wrapped=runtime_wrapEventListener(listener);const runtime_onceWrapper=function(...args){const result=wrapped.apply(this,args);runtime_untrackListener(this,event,listener);return result};runtime_onceWrapper._slothletOriginal=listener;runtime_onceWrapper._slothletResource=wrapped._slothletResource;runtime_trackListener(this,event,listener,runtime_onceWrapper);return original.call(this,event,runtime_onceWrapper)}}function runtime_patchRemoveListener(){const original=EventEmitter.prototype.removeListener;originalMethods.set("removeListener",original);EventEmitter.prototype.removeListener=function(event,listener){const wrapped=runtime_getWrappedListener(this,event,listener);if(wrapped){const result=original.call(this,event,wrapped);runtime_untrackListener(this,event,listener);return result}return original.call(this,event,listener)};EventEmitter.prototype.off=EventEmitter.prototype.removeListener}function runtime_patchRemoveAllListeners(){const original=EventEmitter.prototype.removeAllListeners;originalMethods.set("removeAllListeners",original);EventEmitter.prototype.removeAllListeners=function(event){const emitterTracking=wrappedListeners.get(this);if(emitterTracking){if(event===void 0){for(const[____evt,eventTracking]of emitterTracking.entries()){for(const wrappedListener of eventTracking.values()){wrappedListener._slothletResource=null}}wrappedListeners.delete(this)}else{const eventTracking=emitterTracking.get(event);if(eventTracking){for(const wrappedListener of eventTracking.values()){wrappedListener._slothletResource=null}emitterTracking.delete(event);if(emitterTracking.size===0){wrappedListeners.delete(this)}}}}return original.call(this,event)}}function enableEventEmitterPatching(){if(isPatchingEnabled){return}runtime_patchOn();runtime_patchOnce();runtime_patchPrependListener();runtime_patchPrependOnceListener();runtime_patchRemoveListener();runtime_patchRemoveAllListeners();isPatchingEnabled=true}function disableEventEmitterPatching(){if(!isPatchingEnabled){return}for(const[methodName,originalMethod]of originalMethods.entries()){EventEmitter.prototype[methodName]=originalMethod;if(methodName==="on"){EventEmitter.prototype.addListener=originalMethod}else if(methodName==="removeListener"){EventEmitter.prototype.off=originalMethod}}originalMethods.clear();isPatchingEnabled=false}function cleanupEventEmitterResources(){for(const emitter of trackedEmitters){try{emitter.removeAllListeners()}catch(____error){}}trackedEmitters.clear();wrappedListeners.clear()}export{cleanupEventEmitterResources,disableEventEmitterPatching,enableEventEmitterPatching,setApiContextChecker};
17
+ import{EventEmitter}from"node:events";import{AsyncResource}from"node:async_hooks";let isInApiContext=null;function setApiContextChecker(checker){isInApiContext=checker}const originalMethods=new Map;const wrappedListeners=new Map;const trackedEmitters=new Set;let isPatchingEnabled=false;function runtime_wrapEventListener(listener){const resource=new AsyncResource("slothlet-event-listener");const runtime_wrappedListener=function(...args){return resource.runInAsyncScope(()=>{return listener.apply(this,args)},this)};runtime_wrappedListener._slothletOriginal=listener;runtime_wrappedListener._slothletResource=resource;return runtime_wrappedListener}function runtime_getListenerTracking(emitter){let emitterTracking=wrappedListeners.get(emitter);if(!emitterTracking){emitterTracking=new Map;wrappedListeners.set(emitter,emitterTracking)}return emitterTracking}function runtime_trackListener(emitter,event,originalListener,wrappedListener){const emitterTracking=runtime_getListenerTracking(emitter);let eventTracking=emitterTracking.get(event);if(!eventTracking){eventTracking=new Map;emitterTracking.set(event,eventTracking)}let wrappers=eventTracking.get(originalListener);if(!wrappers){wrappers=[];eventTracking.set(originalListener,wrappers)}wrappers.push(wrappedListener)}function runtime_getWrappedListener(emitter,event,originalListener){const emitterTracking=wrappedListeners.get(emitter);if(!emitterTracking)return void 0;const eventTracking=emitterTracking.get(event);if(!eventTracking)return void 0;const wrappers=eventTracking.get(originalListener);if(!wrappers||wrappers.length===0)return void 0;return wrappers[wrappers.length-1]}function runtime_untrackListener(emitter,event,originalListener){const emitterTracking=wrappedListeners.get(emitter);if(!emitterTracking)return;const eventTracking=emitterTracking.get(event);if(!eventTracking)return;const wrappers=eventTracking.get(originalListener);if(!wrappers||wrappers.length===0)return;const wrappedListener=wrappers.pop();wrappedListener._slothletResource=null;if(wrappers.length===0){eventTracking.delete(originalListener)}if(eventTracking.size===0){emitterTracking.delete(event)}if(emitterTracking.size===0){wrappedListeners.delete(emitter)}}function runtime_untrackSpecificWrapper(emitter,event,originalListener,wrappedListener){const emitterTracking=wrappedListeners.get(emitter);if(!emitterTracking)return;const eventTracking=emitterTracking.get(event);if(!eventTracking)return;const wrappers=eventTracking.get(originalListener);if(!wrappers||wrappers.length===0)return;const idx=wrappers.indexOf(wrappedListener);if(idx===-1)return;wrappers.splice(idx,1);wrappedListener._slothletResource=null;if(wrappers.length===0){eventTracking.delete(originalListener)}if(eventTracking.size===0){emitterTracking.delete(event)}if(emitterTracking.size===0){wrappedListeners.delete(emitter)}}function runtime_shouldWrapListener(listener){if(typeof listener!=="function")return false;if(listener._slothletOriginal)return false;return true}function runtime_maybeTrackEmitter(emitter){if(isInApiContext&&isInApiContext()){trackedEmitters.add(emitter)}}function runtime_patchOn(){const original=EventEmitter.prototype.on;originalMethods.set("on",original);EventEmitter.prototype.on=function(event,listener){runtime_maybeTrackEmitter(this);if(!runtime_shouldWrapListener(listener)){return original.call(this,event,listener)}const wrapped=runtime_wrapEventListener(listener);runtime_trackListener(this,event,listener,wrapped);return original.call(this,event,wrapped)};EventEmitter.prototype.addListener=EventEmitter.prototype.on}function runtime_patchOnce(){const original=EventEmitter.prototype.once;originalMethods.set("once",original);const originalOn=originalMethods.get("on")??EventEmitter.prototype.on;const originalRemove=originalMethods.get("removeListener")??EventEmitter.prototype.removeListener;EventEmitter.prototype.once=function(event,listener){runtime_maybeTrackEmitter(this);if(!runtime_shouldWrapListener(listener)){return original.call(this,event,listener)}const wrapped=runtime_wrapEventListener(listener);const self=this;const runtime_onceWrapper=function(...args){originalRemove.call(self,event,runtime_onceWrapper);runtime_untrackSpecificWrapper(self,event,listener,runtime_onceWrapper);return wrapped.apply(this,args)};runtime_onceWrapper._slothletOriginal=listener;runtime_onceWrapper._slothletResource=wrapped._slothletResource;runtime_onceWrapper.listener=listener;runtime_trackListener(this,event,listener,runtime_onceWrapper);return originalOn.call(this,event,runtime_onceWrapper)}}function runtime_patchPrependListener(){const original=EventEmitter.prototype.prependListener;originalMethods.set("prependListener",original);EventEmitter.prototype.prependListener=function(event,listener){runtime_maybeTrackEmitter(this);if(!runtime_shouldWrapListener(listener)){return original.call(this,event,listener)}const wrapped=runtime_wrapEventListener(listener);runtime_trackListener(this,event,listener,wrapped);return original.call(this,event,wrapped)}}function runtime_patchPrependOnceListener(){const original=EventEmitter.prototype.prependOnceListener;originalMethods.set("prependOnceListener",original);const originalPrepend=originalMethods.get("prependListener")??EventEmitter.prototype.prependListener;const originalRemove=originalMethods.get("removeListener")??EventEmitter.prototype.removeListener;EventEmitter.prototype.prependOnceListener=function(event,listener){runtime_maybeTrackEmitter(this);if(!runtime_shouldWrapListener(listener)){return original.call(this,event,listener)}const wrapped=runtime_wrapEventListener(listener);const self=this;const runtime_onceWrapper=function(...args){originalRemove.call(self,event,runtime_onceWrapper);runtime_untrackSpecificWrapper(self,event,listener,runtime_onceWrapper);return wrapped.apply(this,args)};runtime_onceWrapper._slothletOriginal=listener;runtime_onceWrapper._slothletResource=wrapped._slothletResource;runtime_onceWrapper.listener=listener;runtime_trackListener(this,event,listener,runtime_onceWrapper);return originalPrepend.call(this,event,runtime_onceWrapper)}}function runtime_patchRemoveListener(){const original=EventEmitter.prototype.removeListener;originalMethods.set("removeListener",original);EventEmitter.prototype.removeListener=function(event,listener){const wrapped=runtime_getWrappedListener(this,event,listener);if(wrapped){const result=original.call(this,event,wrapped);runtime_untrackListener(this,event,listener);return result}return original.call(this,event,listener)};EventEmitter.prototype.off=EventEmitter.prototype.removeListener}function runtime_patchRemoveAllListeners(){const original=EventEmitter.prototype.removeAllListeners;originalMethods.set("removeAllListeners",original);EventEmitter.prototype.removeAllListeners=function(event){const emitterTracking=wrappedListeners.get(this);if(emitterTracking){if(event===void 0){for(const[____evt,eventTracking]of emitterTracking.entries()){for(const wrappers of eventTracking.values()){for(const wrappedListener of wrappers){wrappedListener._slothletResource=null}}}wrappedListeners.delete(this)}else{const eventTracking=emitterTracking.get(event);if(eventTracking){for(const wrappers of eventTracking.values()){for(const wrappedListener of wrappers){wrappedListener._slothletResource=null}}emitterTracking.delete(event);if(emitterTracking.size===0){wrappedListeners.delete(this)}}}}return original.call(this,event)}}function enableEventEmitterPatching(){if(isPatchingEnabled){return}runtime_patchOn();runtime_patchOnce();runtime_patchPrependListener();runtime_patchPrependOnceListener();runtime_patchRemoveListener();runtime_patchRemoveAllListeners();isPatchingEnabled=true}function disableEventEmitterPatching(){if(!isPatchingEnabled){return}for(const[methodName,originalMethod]of originalMethods.entries()){EventEmitter.prototype[methodName]=originalMethod;if(methodName==="on"){EventEmitter.prototype.addListener=originalMethod}else if(methodName==="removeListener"){EventEmitter.prototype.off=originalMethod}}originalMethods.clear();isPatchingEnabled=false}function cleanupEventEmitterResources(){for(const emitter of trackedEmitters){try{emitter.removeAllListeners()}catch(____error){}}trackedEmitters.clear();wrappedListeners.clear()}export{cleanupEventEmitterResources,disableEventEmitterPatching,enableEventEmitterPatching,setApiContextChecker};
@@ -0,0 +1,17 @@
1
+ /*
2
+ Copyright 2026 CLDMV/Shinrai
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import{promises as fs}from"node:fs";import path from"node:path";import{SlothletError}from"@cldmv/slothlet/errors";import{validateModuleManifest}from"@cldmv/slothlet/helpers/module-manifest-validator";const DEFAULT_MANIFEST_FILE="slothlet.module.json";const UPWARD_WALK_CAP=20;async function discoverModules(options={}){const scanRoots=await resolveScanRoots(options.scanRoot);const manifestSource=parseManifestSource(options.manifest);const fieldSchema=options.schema??{};const filterFn=options.filter;const prefixes=normalizePrefixes(options.prefix);const candidates=[];for(const root of scanRoots){const mode=await detectScanMode(root);if(mode==="npm"){candidates.push(...await enumerateNpmPackages(root,prefixes))}else{candidates.push(...await enumerateFolderModules(root,prefixes))}}const results=[];const seenRealPaths=new Set;const realPathByNameVersion=new Map;for(const candidate of candidates){let realPath;try{realPath=await fs.realpath(candidate.path)}catch{continue}if(seenRealPaths.has(realPath))continue;seenRealPaths.add(realPath);const pkgJsonPath=path.join(realPath,"package.json");const pkg=await readJsonOrNull(pkgJsonPath);if(!pkg||typeof pkg.name!=="string"||typeof pkg.version!=="string"){continue}const manifestPath=path.join(realPath,manifestSource.file);const manifestRaw=await loadManifestRaw(manifestPath,manifestSource,pkg.name);if(manifestRaw===void 0)continue;const remapped=applySchemaRemap(manifestRaw,fieldSchema);if(manifestSource.isOverride&&remapped.schemaVersion===void 0){remapped.schemaVersion=1}const normalized=validateModuleManifest(remapped,{packageName:pkg.name,packageVersion:pkg.version,packageDescription:pkg.description,packageRoot:realPath,manifestPath});if(typeof filterFn==="function"&&!filterFn(normalized,pkg.name)){continue}const nameVersionKey=`${pkg.name}@${pkg.version}`;if(realPathByNameVersion.has(nameVersionKey)){const otherRealPath=realPathByNameVersion.get(nameVersionKey);if(otherRealPath!==realPath){throw new SlothletError("MODULE_DUPLICATE_NAME_VERSION_MISMATCH",{packageName:pkg.name,version:pkg.version,paths:[otherRealPath,realPath].join(", ")},null,{validationError:true})}}else{realPathByNameVersion.set(nameVersionKey,realPath)}const apiDirAbs=path.resolve(realPath,normalized.apiDir);results.push(Object.freeze({packageName:pkg.name,packageRoot:realPath,mountPath:Object.freeze([...normalized.mountPath]),apiDir:apiDirAbs,manifest:deepFreeze(normalized)}))}return results}async function resolveScanRoots(opt){if(opt===void 0){return[await defaultScanRoot()]}if(typeof opt==="string"){return[path.resolve(opt)]}if(Array.isArray(opt)){return opt.map(p=>path.resolve(p))}throw new SlothletError("INVALID_ARGUMENT",{argument:"scanRoot",expected:"string or string[]",received:typeof opt},null,{validationError:true})}async function defaultScanRoot(){let current=process.cwd();for(let i=0;i<UPWARD_WALK_CAP;i++){try{const stat=await fs.stat(path.join(current,"node_modules"));if(stat.isDirectory())return current}catch{}const parent=path.dirname(current);if(parent===current)break;current=parent}return process.cwd()}async function detectScanMode(root){try{const stat=await fs.stat(path.join(root,"node_modules"));if(stat.isDirectory())return"npm"}catch{}return"folder"}async function enumerateNpmPackages(root,prefixes){const nodeModules=path.join(root,"node_modules");const out=[];let entries;try{entries=await fs.readdir(nodeModules,{withFileTypes:true})}catch{return out}for(const entry of entries){if(!entry.isDirectory()&&!entry.isSymbolicLink())continue;if(entry.name.startsWith("."))continue;if(entry.name.startsWith("@")){const scopeDir=path.join(nodeModules,entry.name);let scopedEntries;try{scopedEntries=await fs.readdir(scopeDir,{withFileTypes:true})}catch{continue}for(const scoped of scopedEntries){if(!scoped.isDirectory()&&!scoped.isSymbolicLink())continue;const fullName=`${entry.name}/${scoped.name}`;if(!matchesPrefix(fullName,prefixes))continue;out.push({path:path.join(scopeDir,scoped.name),packageName:fullName})}}else{if(!matchesPrefix(entry.name,prefixes))continue;out.push({path:path.join(nodeModules,entry.name),packageName:entry.name})}}return out}async function enumerateFolderModules(root,prefixes){const out=[];let entries;try{entries=await fs.readdir(root,{withFileTypes:true})}catch{return out}for(const entry of entries){if(!entry.isDirectory()&&!entry.isSymbolicLink())continue;if(entry.name.startsWith("."))continue;if(!matchesPrefix(entry.name,prefixes))continue;out.push({path:path.join(root,entry.name),packageName:entry.name})}return out}function normalizePrefixes(opt){if(opt===void 0)return null;if(typeof opt==="string")return[opt];if(Array.isArray(opt)){if(opt.length===0)return null;return opt.slice()}throw new SlothletError("INVALID_ARGUMENT",{argument:"prefix",expected:"string or string[]",received:typeof opt},null,{validationError:true})}function matchesPrefix(name,prefixes){if(!prefixes)return true;for(const p of prefixes){if(name.startsWith(p))return true}return false}function parseManifestSource(opt){if(opt===void 0){return{file:DEFAULT_MANIFEST_FILE,subkey:null,isOverride:false}}if(typeof opt!=="string"||opt.length===0){throw new SlothletError("INVALID_ARGUMENT",{argument:"manifest",expected:"string",received:typeof opt},null,{validationError:true})}const hashIdx=opt.indexOf("#");if(hashIdx===-1){return{file:opt,subkey:null,isOverride:opt!==DEFAULT_MANIFEST_FILE}}const file=opt.slice(0,hashIdx);const subkey=opt.slice(hashIdx+1);if(file.length===0||subkey.length===0){throw new SlothletError("INVALID_ARGUMENT",{argument:"manifest",expected:"<file>#<dotted.key> with both parts non-empty",received:opt},null,{validationError:true})}return{file,subkey,isOverride:true}}async function readJsonOrNull(filePath){let content;try{content=await fs.readFile(filePath,"utf8")}catch{return null}try{return JSON.parse(content)}catch{return null}}async function loadManifestRaw(manifestPath,source,packageName){let content;try{content=await fs.readFile(manifestPath,"utf8")}catch{return void 0}let parsed;try{parsed=JSON.parse(content)}catch(err){throw new SlothletError("MODULE_MANIFEST_INVALID",{packageName,manifestPath,reason:`JSON parse error: ${err.message}`},err,{validationError:true})}if(source.subkey===null){return parsed}const parts=source.subkey.split(".");let cur=parsed;for(const part of parts){if(cur===null||typeof cur!=="object"||!(part in cur)){return void 0}cur=cur[part]}if(cur===null||typeof cur!=="object"||Array.isArray(cur)){return void 0}return cur}function applySchemaRemap(raw,schemaMap){if(!schemaMap||Object.keys(schemaMap).length===0){return raw}const out={...raw};for(const[canonical,legacy]of Object.entries(schemaMap)){if(legacy===canonical)continue;if(out[canonical]!==void 0)continue;if(legacy in out){out[canonical]=out[legacy];delete out[legacy]}}return out}function deepFreeze(obj){if(obj===null||typeof obj!=="object"||Object.isFrozen(obj))return obj;Object.freeze(obj);for(const key of Object.keys(obj)){const value=obj[key];if(value!==null&&typeof value==="object"){deepFreeze(value)}}return obj}export{discoverModules};
@@ -0,0 +1,17 @@
1
+ /*
2
+ Copyright 2026 CLDMV/Shinrai
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import path from"node:path";import{SlothletError}from"@cldmv/slothlet/errors";const RESERVED_MOUNTPATH_ROOTS=Object.freeze(new Set(["slothlet","shutdown","destroy"]));const ALLOWED_TOP_LEVEL_FIELDS=Object.freeze(new Set(["schemaVersion","name","version","description","mountPath","apiDir","kind","priority","dependencies","permissions","metadata"]));const PERMISSION_EFFECT_VALUES=Object.freeze(new Set(["allow","deny"]));function validateModuleManifest(manifest,packageContext){if(!manifest||typeof manifest!=="object"||Array.isArray(manifest)){throw new SlothletError("MODULE_MANIFEST_INVALID",{packageName:packageContext?.packageName??"<unknown>",manifestPath:packageContext?.manifestPath??"<unknown>",reason:"manifest must be a JSON object"},null,{validationError:true})}const{packageName,packageVersion,packageDescription,packageRoot,manifestPath}=packageContext;for(const key of Object.keys(manifest)){if(!ALLOWED_TOP_LEVEL_FIELDS.has(key)){throw new SlothletError("MODULE_MANIFEST_UNKNOWN_FIELD",{packageName,manifestPath,field:key},null,{validationError:true})}}if(manifest.schemaVersion===void 0){throw new SlothletError("MODULE_MANIFEST_INVALID",{packageName,manifestPath,reason:"missing required field 'schemaVersion'"},null,{validationError:true})}if(manifest.schemaVersion!==1){throw new SlothletError("MODULE_VERSION_UNSUPPORTED",{packageName,schemaVersion:String(manifest.schemaVersion)},null,{validationError:true})}if(manifest.mountPath===void 0){throw new SlothletError("MODULE_MANIFEST_INVALID",{packageName,manifestPath,reason:"missing required field 'mountPath'"},null,{validationError:true})}const mountPathSegments=normalizeMountPath(manifest.mountPath,packageName,manifestPath);if(RESERVED_MOUNTPATH_ROOTS.has(mountPathSegments[0])){throw new SlothletError("MODULE_RESERVED_MOUNTPATH",{packageName,mountPathRoot:mountPathSegments[0],mountPath:mountPathSegments.join(".")},null,{validationError:true})}if(manifest.apiDir===void 0){throw new SlothletError("MODULE_MANIFEST_INVALID",{packageName,manifestPath,reason:"missing required field 'apiDir'"},null,{validationError:true})}if(typeof manifest.apiDir!=="string"||manifest.apiDir.length===0){throw new SlothletError("MODULE_MANIFEST_INVALID",{packageName,manifestPath,reason:"field 'apiDir' must be a non-empty string"},null,{validationError:true})}validateApiDirContainment(manifest.apiDir,packageRoot,packageName);if(manifest.name!==void 0){if(typeof manifest.name!=="string"){throw new SlothletError("MODULE_MANIFEST_INVALID",{packageName,manifestPath,reason:"field 'name' must be a string"},null,{validationError:true})}if(manifest.name!==packageName){throw new SlothletError("MODULE_MANIFEST_NAME_MISMATCH",{packageName,manifestName:manifest.name},null,{validationError:true})}}if(manifest.version!==void 0){if(typeof manifest.version!=="string"){throw new SlothletError("MODULE_MANIFEST_INVALID",{packageName,manifestPath,reason:"field 'version' must be a string"},null,{validationError:true})}if(manifest.version!==packageVersion){throw new SlothletError("MODULE_MANIFEST_VERSION_MISMATCH",{packageName,manifestVersion:manifest.version,packageVersion},null,{validationError:true})}}if(manifest.description!==void 0&&typeof manifest.description!=="string"){throw new SlothletError("MODULE_MANIFEST_INVALID",{packageName,manifestPath,reason:"field 'description' must be a string"},null,{validationError:true})}if(manifest.kind!==void 0&&typeof manifest.kind!=="string"){throw new SlothletError("MODULE_MANIFEST_INVALID",{packageName,manifestPath,reason:"field 'kind' must be a string"},null,{validationError:true})}if(manifest.priority!==void 0&&(typeof manifest.priority!=="number"||!Number.isFinite(manifest.priority))){throw new SlothletError("MODULE_MANIFEST_INVALID",{packageName,manifestPath,reason:"field 'priority' must be a finite number"},null,{validationError:true})}if(manifest.dependencies!==void 0){if(typeof manifest.dependencies!=="object"||manifest.dependencies===null||Array.isArray(manifest.dependencies)){throw new SlothletError("MODULE_MANIFEST_INVALID",{packageName,manifestPath,reason:"field 'dependencies' must be a plain object"},null,{validationError:true})}for(const[depName,depValue]of Object.entries(manifest.dependencies)){if(typeof depValue!=="string"){throw new SlothletError("MODULE_MANIFEST_INVALID",{packageName,manifestPath,reason:`dependency '${depName}' must be a string version range`},null,{validationError:true})}}}if(manifest.permissions!==void 0){validatePermissions(manifest.permissions,packageName,manifestPath)}if(manifest.metadata!==void 0){if(typeof manifest.metadata!=="object"||manifest.metadata===null||Array.isArray(manifest.metadata)){throw new SlothletError("MODULE_MANIFEST_INVALID",{packageName,manifestPath,reason:"field 'metadata' must be a plain object"},null,{validationError:true})}}return{schemaVersion:1,name:packageName,version:packageVersion,description:manifest.description??packageDescription,mountPath:mountPathSegments,apiDir:manifest.apiDir,kind:manifest.kind,priority:manifest.priority??0,dependencies:manifest.dependencies,permissions:manifest.permissions,metadata:manifest.metadata}}function normalizeMountPath(mountPath,packageName,manifestPath){if(typeof mountPath==="string"){if(mountPath.length===0){throw new SlothletError("MODULE_MANIFEST_INVALID",{packageName,manifestPath,reason:"field 'mountPath' must be a non-empty string or non-empty array of non-empty strings"},null,{validationError:true})}return mountPath.split(".")}if(Array.isArray(mountPath)){if(mountPath.length===0){throw new SlothletError("MODULE_MANIFEST_INVALID",{packageName,manifestPath,reason:"field 'mountPath' must be a non-empty string or non-empty array of non-empty strings"},null,{validationError:true})}for(const segment of mountPath){if(typeof segment!=="string"||segment.length===0){throw new SlothletError("MODULE_MANIFEST_INVALID",{packageName,manifestPath,reason:"field 'mountPath' array entries must all be non-empty strings"},null,{validationError:true})}}return mountPath.slice()}throw new SlothletError("MODULE_MANIFEST_INVALID",{packageName,manifestPath,reason:"field 'mountPath' must be a non-empty string or non-empty array of non-empty strings"},null,{validationError:true})}function validateApiDirContainment(apiDir,packageRoot,packageName){const resolved=path.resolve(packageRoot,apiDir);const rootWithSep=packageRoot.endsWith(path.sep)?packageRoot:packageRoot+path.sep;if(resolved!==packageRoot&&!resolved.startsWith(rootWithSep)){throw new SlothletError("MODULE_PATH_TRAVERSAL",{packageName,apiDir,resolvedPath:resolved,packageRoot},null,{validationError:true})}}function validatePermissions(permissions,packageName,manifestPath){if(!Array.isArray(permissions)){throw new SlothletError("MODULE_MANIFEST_INVALID",{packageName,manifestPath,reason:"field 'permissions' must be an array of rule objects"},null,{validationError:true})}for(let i=0;i<permissions.length;i++){const rule=permissions[i];if(!rule||typeof rule!=="object"||Array.isArray(rule)){throw new SlothletError("MODULE_MANIFEST_INVALID",{packageName,manifestPath,reason:`permissions[${i}] must be a rule object`},null,{validationError:true})}if(typeof rule.caller!=="string"||rule.caller.length===0){throw new SlothletError("MODULE_MANIFEST_INVALID",{packageName,manifestPath,reason:`permissions[${i}].caller must be a non-empty string`},null,{validationError:true})}if(typeof rule.target!=="string"||rule.target.length===0){throw new SlothletError("MODULE_MANIFEST_INVALID",{packageName,manifestPath,reason:`permissions[${i}].target must be a non-empty string`},null,{validationError:true})}if(!PERMISSION_EFFECT_VALUES.has(rule.effect)){throw new SlothletError("MODULE_MANIFEST_INVALID",{packageName,manifestPath,reason:`permissions[${i}].effect must be "allow" or "deny"`},null,{validationError:true})}}}export{validateModuleManifest};
@@ -0,0 +1,17 @@
1
+ /*
2
+ Copyright 2026 CLDMV/Shinrai
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ function sortModules(results,comparator){const cmp=typeof comparator==="function"?comparator:defaultModuleComparator;return[...results].sort(cmp)}function defaultModuleComparator(a,b){const pa=typeof a?.manifest?.priority==="number"?a.manifest.priority:0;const pb=typeof b?.manifest?.priority==="number"?b.manifest.priority:0;if(pa!==pb)return pb-pa;const na=a?.packageName??"";const nb=b?.packageName??"";if(na<nb)return-1;if(na>nb)return 1;return 0}export{sortModules};
@@ -405,7 +405,29 @@
405
405
  "PERM_RULE_CONDITION_INVALID": "rule.condition muss ein einfaches Objekt, eine Funktion oder ein Array sein, bei dem jeder Eintrag ein einfaches Objekt oder eine Funktion ist",
406
406
  "BRACE_EXPANSION_MAX_DEPTH": "Klammerauflösung hat die maximale Tiefe von {maxDepth} überschritten",
407
407
  "HINT_BRACE_EXPANSION_MAX_DEPTH": "Verringern Sie die Verschachtelung in Ihren Klammermustern oder erhöhen Sie die maxDepth-Option.",
408
- "PERMISSION_MANAGER_NOT_AVAILABLE": "Berechtigungsmanager ist nicht in dieser Slothlet-Instanz verfügbar"
408
+ "PERMISSION_MANAGER_NOT_AVAILABLE": "Berechtigungsmanager ist nicht in dieser Slothlet-Instanz verfügbar",
409
+ "MODULE_MANIFEST_NOT_FOUND": "Modul-Manifest nicht gefunden für Paket '{packageName}': '{manifestPath}' erwartet, aber die Datei existiert nicht.",
410
+ "HINT_MODULE_MANIFEST_NOT_FOUND": "Plugin-Module müssen eine 'slothlet.module.json' im Paketwurzelverzeichnis ausliefern (oder die über die `manifest`-Überschreibung angegebene Datei/Schlüssel). Überprüfen Sie, ob die Datei im Paket existiert und der Override-Locator korrekt ist.",
411
+ "MODULE_MANIFEST_INVALID": "Modul-Manifest ist ungültig für Paket '{packageName}' unter '{manifestPath}': {reason}.",
412
+ "HINT_MODULE_MANIFEST_INVALID": "Öffnen Sie das Manifest und stellen Sie sicher, dass es gültiges JSON ist und dem Slothlet-Modul-Schema entspricht (siehe schemas/slothlet.module.schema.json). Erforderliche Felder: schemaVersion, mountPath, apiDir.",
413
+ "MODULE_MANIFEST_UNKNOWN_FIELD": "Modul-Manifest für Paket '{packageName}' enthält nicht erkanntes Feld auf oberster Ebene '{field}'. Unbekannte Felder werden abgelehnt; verwenden Sie den dedizierten 'metadata'-Block für entwicklerdefinierte Zusätze.",
414
+ "HINT_MODULE_MANIFEST_UNKNOWN_FIELD": "Wenn '{field}' ein Tippfehler ist, korrigieren Sie ihn. Wenn es sich um beabsichtigte Entwicklermetadaten handelt, verschieben Sie sie in das 'metadata'-Objekt.",
415
+ "MODULE_MANIFEST_NAME_MISMATCH": "Modul-Manifest für Paket '{packageName}' deklariert Namen '{manifestName}', der nicht mit dem Namen in package.json '{packageName}' übereinstimmt.",
416
+ "HINT_MODULE_MANIFEST_NAME_MISMATCH": "Das 'name'-Feld in slothlet.module.json ist optional, muss aber mit package.json übereinstimmen, wenn vorhanden. Entfernen Sie entweder das 'name'-Feld im Manifest oder gleichen Sie es mit package.json ab.",
417
+ "MODULE_MANIFEST_VERSION_MISMATCH": "Modul-Manifest für Paket '{packageName}' deklariert Version '{manifestVersion}', die nicht mit der Version in package.json '{packageVersion}' übereinstimmt.",
418
+ "HINT_MODULE_MANIFEST_VERSION_MISMATCH": "Das 'version'-Feld in slothlet.module.json ist optional, muss aber mit package.json übereinstimmen, wenn vorhanden. Entfernen Sie entweder das 'version'-Feld im Manifest oder gleichen Sie es mit package.json ab.",
419
+ "MODULE_PATH_TRAVERSAL": "Modul-Paket '{packageName}' hat apiDir '{apiDir}', das außerhalb des Paketwurzelverzeichnisses aufgelöst wird ('{resolvedPath}' liegt nicht unter '{packageRoot}').",
420
+ "HINT_MODULE_PATH_TRAVERSAL": "apiDir muss ein relativer Pfad sein, der innerhalb des Pakets bleibt. Entfernen Sie '..'-Segmente und absolute Pfade.",
421
+ "MODULE_VERSION_UNSUPPORTED": "Modul-Manifest für Paket '{packageName}' verwendet nicht unterstützte schemaVersion '{schemaVersion}'. Unterstützt: 1.",
422
+ "HINT_MODULE_VERSION_UNSUPPORTED": "Aktualisieren Sie slothlet auf eine Version, die schemaVersion {schemaVersion} unterstützt, oder aktualisieren Sie das Manifest auf schemaVersion 1.",
423
+ "MODULE_PACKAGE_NOT_FOUND": "Modul-Paket '{packageName}' wurde nicht im Discovery-Cache gefunden. {hint}",
424
+ "HINT_MODULE_PACKAGE_NOT_FOUND": "Entweder ist das Paket nicht installiert, der scanRoot deckt seinen Speicherort nicht ab, oder es wurde noch kein discover()-Aufruf durchgeführt. addModule(name) ruft discover() lazy mit scanRoot=process.cwd() auf, wenn der Cache leer ist.",
425
+ "MODULE_RESERVED_MOUNTPATH": "Modul-Paket '{packageName}' deklariert reservierte mountPath-Wurzel '{mountPathRoot}' (aus mountPath '{mountPath}'). Reservierte Wurzeln: slothlet, shutdown, destroy.",
426
+ "HINT_MODULE_RESERVED_MOUNTPATH": "Reservierte mountPath-Wurzeln werden von der integrierten Oberfläche von slothlet verwendet und können nicht von Modulen beansprucht werden. Wählen Sie ein anderes Wurzelsegment.",
427
+ "MODULE_DUPLICATE_NAME_VERSION_MISMATCH": "Modul-Paket '{packageName}' Version '{version}' wurde an mehreren verschiedenen tatsächlichen Pfaden gefunden: {paths}.",
428
+ "HINT_MODULE_DUPLICATE_NAME_VERSION_MISMATCH": "Dies ist mit ziemlicher Sicherheit eine Fehlkonfiguration — dasselbe Paket in derselben Version, das an zwei nicht verwandten Orten installiert ist. Untersuchen Sie, welche Installation gewünscht ist, und entfernen Sie die andere.",
429
+ "MODULE_MOUNT_COLLISION": "Modul-Paket '{packageName}' kann nicht unter '{mountPath}' eingebunden werden: Pfad ist bereits von Modul '{existingModuleID}' belegt und collisionMode ist '{collisionMode}'.",
430
+ "HINT_MODULE_MOUNT_COLLISION": "Ändern Sie entweder den mountPath des Manifests, übergeben Sie `collisionMode: \"merge\"` an `addModule()` / `addModules()` um Koexistenz zu erlauben, oder entfernen Sie zuerst das bestehende Modul mit `removeModule()`."
409
431
  },
410
432
  "metadata": {
411
433
  "code": "de-de",
@@ -405,7 +405,29 @@
405
405
  "PERM_RULE_CONDITION_INVALID": "rule.condition must be a plain object, a function, or an array where each entry is a plain object or function",
406
406
  "BRACE_EXPANSION_MAX_DEPTH": "Brace expansion exceeded maximum depth of {maxDepth}",
407
407
  "HINT_BRACE_EXPANSION_MAX_DEPTH": "Reduce nesting in your brace patterns or increase the maxDepth option.",
408
- "PERMISSION_MANAGER_NOT_AVAILABLE": "Permission manager is not available in this Slothlet instance"
408
+ "PERMISSION_MANAGER_NOT_AVAILABLE": "Permission manager is not available in this Slothlet instance",
409
+ "MODULE_MANIFEST_NOT_FOUND": "Module manifest not found for package '{packageName}': expected '{manifestPath}' but the file does not exist.",
410
+ "HINT_MODULE_MANIFEST_NOT_FOUND": "Plugin modules must ship a 'slothlet.module.json' at the package root (or the file/key specified via the `manifest` override). Check that the file exists in the package and that the override locator is correct.",
411
+ "MODULE_MANIFEST_INVALID": "Module manifest is invalid for package '{packageName}' at '{manifestPath}': {reason}.",
412
+ "HINT_MODULE_MANIFEST_INVALID": "Open the manifest and ensure it is valid JSON and matches the slothlet module schema (see schemas/slothlet.module.schema.json). Required fields: schemaVersion, mountPath, apiDir.",
413
+ "MODULE_MANIFEST_UNKNOWN_FIELD": "Module manifest for package '{packageName}' contains unrecognised top-level field '{field}'. Unknown fields are rejected; use the dedicated 'metadata' block for developer-defined extras.",
414
+ "HINT_MODULE_MANIFEST_UNKNOWN_FIELD": "If '{field}' is a typo, fix it. If it is intentional developer metadata, move it inside the 'metadata' object.",
415
+ "MODULE_MANIFEST_NAME_MISMATCH": "Module manifest for package '{packageName}' declares name '{manifestName}' which does not match package.json name '{packageName}'.",
416
+ "HINT_MODULE_MANIFEST_NAME_MISMATCH": "The 'name' field in slothlet.module.json is optional but must match package.json when present. Either remove the manifest 'name' field or align it with package.json.",
417
+ "MODULE_MANIFEST_VERSION_MISMATCH": "Module manifest for package '{packageName}' declares version '{manifestVersion}' which does not match package.json version '{packageVersion}'.",
418
+ "HINT_MODULE_MANIFEST_VERSION_MISMATCH": "The 'version' field in slothlet.module.json is optional but must match package.json when present. Either remove the manifest 'version' field or align it with package.json.",
419
+ "MODULE_PATH_TRAVERSAL": "Module package '{packageName}' has apiDir '{apiDir}' that resolves outside the package root ('{resolvedPath}' is not under '{packageRoot}').",
420
+ "HINT_MODULE_PATH_TRAVERSAL": "apiDir must be a relative path that stays inside the package. Remove '..' segments and absolute paths.",
421
+ "MODULE_VERSION_UNSUPPORTED": "Module manifest for package '{packageName}' uses unsupported schemaVersion '{schemaVersion}'. Supported: 1.",
422
+ "HINT_MODULE_VERSION_UNSUPPORTED": "Upgrade slothlet to a version that supports schemaVersion {schemaVersion}, or update the manifest to use schemaVersion 1.",
423
+ "MODULE_PACKAGE_NOT_FOUND": "Module package '{packageName}' was not found in the discovery cache. {hint}",
424
+ "HINT_MODULE_PACKAGE_NOT_FOUND": "Either the package is not installed, the scanRoot does not cover its location, or no discover() call has been made yet. addModule(name) will lazily run discover() with scanRoot=process.cwd() if the cache is empty.",
425
+ "MODULE_RESERVED_MOUNTPATH": "Module package '{packageName}' declares reserved mountPath root '{mountPathRoot}' (from mountPath '{mountPath}'). Reserved roots: slothlet, shutdown, destroy.",
426
+ "HINT_MODULE_RESERVED_MOUNTPATH": "Reserved mountPath roots are used by slothlet's built-in surface and cannot be claimed by modules. Choose a different root segment.",
427
+ "MODULE_DUPLICATE_NAME_VERSION_MISMATCH": "Module package '{packageName}' version '{version}' was found at multiple distinct real paths: {paths}.",
428
+ "HINT_MODULE_DUPLICATE_NAME_VERSION_MISMATCH": "This is almost certainly a misconfiguration — the same package at the same version installed in two unrelated locations. Investigate which install is intended and remove the other.",
429
+ "MODULE_MOUNT_COLLISION": "Module package '{packageName}' cannot mount at '{mountPath}': path is already occupied by module '{existingModuleID}' and collisionMode is '{collisionMode}'.",
430
+ "HINT_MODULE_MOUNT_COLLISION": "Either change the manifest's mountPath, pass `collisionMode: \"merge\"` to `addModule()` / `addModules()` to allow coexistence, or call `removeModule()` on the existing module first."
409
431
  },
410
432
  "metadata": {
411
433
  "code": "en-gb",
@@ -405,7 +405,29 @@
405
405
  "PERM_RULE_CONDITION_INVALID": "rule.condition must be a plain object, a function, or an array where each entry is a plain object or function",
406
406
  "BRACE_EXPANSION_MAX_DEPTH": "Brace expansion exceeded maximum depth of {maxDepth}",
407
407
  "HINT_BRACE_EXPANSION_MAX_DEPTH": "Reduce nesting in your brace patterns or increase the maxDepth option.",
408
- "PERMISSION_MANAGER_NOT_AVAILABLE": "Permission manager is not available in this Slothlet instance"
408
+ "PERMISSION_MANAGER_NOT_AVAILABLE": "Permission manager is not available in this Slothlet instance",
409
+ "MODULE_MANIFEST_NOT_FOUND": "Module manifest not found for package '{packageName}': expected '{manifestPath}' but the file does not exist.",
410
+ "HINT_MODULE_MANIFEST_NOT_FOUND": "Plugin modules must ship a 'slothlet.module.json' at the package root (or the file/key specified via the `manifest` override). Check that the file exists in the package and that the override locator is correct.",
411
+ "MODULE_MANIFEST_INVALID": "Module manifest is invalid for package '{packageName}' at '{manifestPath}': {reason}.",
412
+ "HINT_MODULE_MANIFEST_INVALID": "Open the manifest and ensure it is valid JSON and matches the slothlet module schema (see schemas/slothlet.module.schema.json). Required fields: schemaVersion, mountPath, apiDir.",
413
+ "MODULE_MANIFEST_UNKNOWN_FIELD": "Module manifest for package '{packageName}' contains unrecognized top-level field '{field}'. Unknown fields are rejected; use the dedicated 'metadata' block for developer-defined extras.",
414
+ "HINT_MODULE_MANIFEST_UNKNOWN_FIELD": "If '{field}' is a typo, fix it. If it is intentional developer metadata, move it inside the 'metadata' object.",
415
+ "MODULE_MANIFEST_NAME_MISMATCH": "Module manifest for package '{packageName}' declares name '{manifestName}' which does not match package.json name '{packageName}'.",
416
+ "HINT_MODULE_MANIFEST_NAME_MISMATCH": "The 'name' field in slothlet.module.json is optional but must match package.json when present. Either remove the manifest 'name' field or align it with package.json.",
417
+ "MODULE_MANIFEST_VERSION_MISMATCH": "Module manifest for package '{packageName}' declares version '{manifestVersion}' which does not match package.json version '{packageVersion}'.",
418
+ "HINT_MODULE_MANIFEST_VERSION_MISMATCH": "The 'version' field in slothlet.module.json is optional but must match package.json when present. Either remove the manifest 'version' field or align it with package.json.",
419
+ "MODULE_PATH_TRAVERSAL": "Module package '{packageName}' has apiDir '{apiDir}' that resolves outside the package root ('{resolvedPath}' is not under '{packageRoot}').",
420
+ "HINT_MODULE_PATH_TRAVERSAL": "apiDir must be a relative path that stays inside the package. Remove '..' segments and absolute paths.",
421
+ "MODULE_VERSION_UNSUPPORTED": "Module manifest for package '{packageName}' uses unsupported schemaVersion '{schemaVersion}'. Supported: 1.",
422
+ "HINT_MODULE_VERSION_UNSUPPORTED": "Upgrade slothlet to a version that supports schemaVersion {schemaVersion}, or update the manifest to use schemaVersion 1.",
423
+ "MODULE_PACKAGE_NOT_FOUND": "Module package '{packageName}' was not found in the discovery cache. {hint}",
424
+ "HINT_MODULE_PACKAGE_NOT_FOUND": "Either the package is not installed, the scanRoot does not cover its location, or no discover() call has been made yet. addModule(name) will lazily run discover() with scanRoot=process.cwd() if the cache is empty.",
425
+ "MODULE_RESERVED_MOUNTPATH": "Module package '{packageName}' declares reserved mountPath root '{mountPathRoot}' (from mountPath '{mountPath}'). Reserved roots: slothlet, shutdown, destroy.",
426
+ "HINT_MODULE_RESERVED_MOUNTPATH": "Reserved mountPath roots are used by slothlet's built-in surface and cannot be claimed by modules. Choose a different root segment.",
427
+ "MODULE_DUPLICATE_NAME_VERSION_MISMATCH": "Module package '{packageName}' version '{version}' was found at multiple distinct real paths: {paths}.",
428
+ "HINT_MODULE_DUPLICATE_NAME_VERSION_MISMATCH": "This is almost certainly a misconfiguration — the same package at the same version installed in two unrelated locations. Investigate which install is intended and remove the other.",
429
+ "MODULE_MOUNT_COLLISION": "Module package '{packageName}' cannot mount at '{mountPath}': path is already occupied by module '{existingModuleID}' and collisionMode is '{collisionMode}'.",
430
+ "HINT_MODULE_MOUNT_COLLISION": "Either change the manifest's mountPath, pass `collisionMode: \"merge\"` to `addModule()` / `addModules()` to allow coexistence, or call `removeModule()` on the existing module first."
409
431
  },
410
432
  "metadata": {
411
433
  "code": "en-us",
@@ -405,7 +405,29 @@
405
405
  "PERM_RULE_CONDITION_INVALID": "rule.condition debe ser un objeto plano, una función o un arreglo donde cada entrada sea un objeto plano o una función",
406
406
  "BRACE_EXPANSION_MAX_DEPTH": "La expansión de llaves superó la profundidad máxima de {maxDepth}",
407
407
  "HINT_BRACE_EXPANSION_MAX_DEPTH": "Reduce el anidamiento en tus patrones de llaves o aumenta la opción maxDepth.",
408
- "PERMISSION_MANAGER_NOT_AVAILABLE": "El administrador de permisos no está disponible en esta instancia de Slothlet"
408
+ "PERMISSION_MANAGER_NOT_AVAILABLE": "El administrador de permisos no está disponible en esta instancia de Slothlet",
409
+ "MODULE_MANIFEST_NOT_FOUND": "Manifiesto de módulo no encontrado para el paquete '{packageName}': se esperaba '{manifestPath}' pero el archivo no existe.",
410
+ "HINT_MODULE_MANIFEST_NOT_FOUND": "Los módulos plugin deben incluir un 'slothlet.module.json' en la raíz del paquete (o el archivo/clave especificado mediante la sobrescritura `manifest`). Verifique que el archivo exista en el paquete y que el localizador de sobrescritura sea correcto.",
411
+ "MODULE_MANIFEST_INVALID": "El manifiesto del módulo no es válido para el paquete '{packageName}' en '{manifestPath}': {reason}.",
412
+ "HINT_MODULE_MANIFEST_INVALID": "Abra el manifiesto y asegúrese de que sea JSON válido y coincida con el esquema del módulo slothlet (consulte schemas/slothlet.module.schema.json). Campos requeridos: schemaVersion, mountPath, apiDir.",
413
+ "MODULE_MANIFEST_UNKNOWN_FIELD": "El manifiesto del módulo para el paquete '{packageName}' contiene un campo de nivel superior no reconocido '{field}'. Los campos desconocidos son rechazados; use el bloque 'metadata' dedicado para extras definidos por el desarrollador.",
414
+ "HINT_MODULE_MANIFEST_UNKNOWN_FIELD": "Si '{field}' es un error tipográfico, corríjalo. Si son metadatos intencionales del desarrollador, muévalos dentro del objeto 'metadata'.",
415
+ "MODULE_MANIFEST_NAME_MISMATCH": "El manifiesto del módulo para el paquete '{packageName}' declara el nombre '{manifestName}' que no coincide con el nombre en package.json '{packageName}'.",
416
+ "HINT_MODULE_MANIFEST_NAME_MISMATCH": "El campo 'name' en slothlet.module.json es opcional pero debe coincidir con package.json cuando está presente. Elimine el campo 'name' del manifiesto o alinéelo con package.json.",
417
+ "MODULE_MANIFEST_VERSION_MISMATCH": "El manifiesto del módulo para el paquete '{packageName}' declara la versión '{manifestVersion}' que no coincide con la versión en package.json '{packageVersion}'.",
418
+ "HINT_MODULE_MANIFEST_VERSION_MISMATCH": "El campo 'version' en slothlet.module.json es opcional pero debe coincidir con package.json cuando está presente. Elimine el campo 'version' del manifiesto o alinéelo con package.json.",
419
+ "MODULE_PATH_TRAVERSAL": "El paquete de módulo '{packageName}' tiene apiDir '{apiDir}' que se resuelve fuera de la raíz del paquete ('{resolvedPath}' no está bajo '{packageRoot}').",
420
+ "HINT_MODULE_PATH_TRAVERSAL": "apiDir debe ser una ruta relativa que permanezca dentro del paquete. Elimine los segmentos '..' y rutas absolutas.",
421
+ "MODULE_VERSION_UNSUPPORTED": "El manifiesto del módulo para el paquete '{packageName}' utiliza una schemaVersion no compatible '{schemaVersion}'. Compatibles: 1.",
422
+ "HINT_MODULE_VERSION_UNSUPPORTED": "Actualice slothlet a una versión que sea compatible con schemaVersion {schemaVersion}, o actualice el manifiesto para usar schemaVersion 1.",
423
+ "MODULE_PACKAGE_NOT_FOUND": "Paquete de módulo '{packageName}' no encontrado en la caché de descubrimiento. {hint}",
424
+ "HINT_MODULE_PACKAGE_NOT_FOUND": "El paquete no está instalado, scanRoot no cubre su ubicación, o aún no se ha realizado una llamada a discover(). addModule(name) ejecutará discover() de forma diferida con scanRoot=process.cwd() si la caché está vacía.",
425
+ "MODULE_RESERVED_MOUNTPATH": "El paquete de módulo '{packageName}' declara la raíz reservada de mountPath '{mountPathRoot}' (de mountPath '{mountPath}'). Raíces reservadas: slothlet, shutdown, destroy.",
426
+ "HINT_MODULE_RESERVED_MOUNTPATH": "Las raíces reservadas de mountPath son utilizadas por la superficie integrada de slothlet y no pueden ser reclamadas por módulos. Elija un segmento raíz diferente.",
427
+ "MODULE_DUPLICATE_NAME_VERSION_MISMATCH": "El paquete de módulo '{packageName}' versión '{version}' se encontró en múltiples rutas reales distintas: {paths}.",
428
+ "HINT_MODULE_DUPLICATE_NAME_VERSION_MISMATCH": "Esto es casi con certeza una configuración incorrecta — el mismo paquete en la misma versión instalado en dos ubicaciones no relacionadas. Investigue cuál instalación es la deseada y elimine la otra.",
429
+ "MODULE_MOUNT_COLLISION": "El paquete de módulo '{packageName}' no se puede montar en '{mountPath}': la ruta ya está ocupada por el módulo '{existingModuleID}' y collisionMode es '{collisionMode}'.",
430
+ "HINT_MODULE_MOUNT_COLLISION": "Cambie el mountPath del manifiesto, pase `collisionMode: \"merge\"` a `addModule()` / `addModules()` para permitir la coexistencia, o llame primero a `removeModule()` en el módulo existente."
409
431
  },
410
432
  "metadata": {
411
433
  "code": "es-es",
@@ -405,7 +405,29 @@
405
405
  "PERM_RULE_CONDITION_INVALID": "rule.condition debe ser un objeto plano, una función o un arreglo donde cada entrada sea un objeto plano o una función",
406
406
  "BRACE_EXPANSION_MAX_DEPTH": "La expansión de llaves superó la profundidad máxima de {maxDepth}",
407
407
  "HINT_BRACE_EXPANSION_MAX_DEPTH": "Reduce el anidamiento en tus patrones de llaves o aumenta la opción maxDepth.",
408
- "PERMISSION_MANAGER_NOT_AVAILABLE": "El administrador de permisos no está disponible en esta instancia de Slothlet"
408
+ "PERMISSION_MANAGER_NOT_AVAILABLE": "El administrador de permisos no está disponible en esta instancia de Slothlet",
409
+ "MODULE_MANIFEST_NOT_FOUND": "Manifiesto de módulo no encontrado para el paquete '{packageName}': se esperaba '{manifestPath}' pero el archivo no existe.",
410
+ "HINT_MODULE_MANIFEST_NOT_FOUND": "Los módulos plugin deben incluir un 'slothlet.module.json' en la raíz del paquete (o el archivo/clave especificado mediante la sobrescritura `manifest`). Verifica que el archivo exista en el paquete y que el localizador de sobrescritura sea correcto.",
411
+ "MODULE_MANIFEST_INVALID": "El manifiesto del módulo no es válido para el paquete '{packageName}' en '{manifestPath}': {reason}.",
412
+ "HINT_MODULE_MANIFEST_INVALID": "Abre el manifiesto y asegúrate de que sea JSON válido y coincida con el esquema del módulo slothlet (consulta schemas/slothlet.module.schema.json). Campos requeridos: schemaVersion, mountPath, apiDir.",
413
+ "MODULE_MANIFEST_UNKNOWN_FIELD": "El manifiesto del módulo para el paquete '{packageName}' contiene un campo de nivel superior no reconocido '{field}'. Los campos desconocidos son rechazados; usa el bloque 'metadata' dedicado para extras definidos por el desarrollador.",
414
+ "HINT_MODULE_MANIFEST_UNKNOWN_FIELD": "Si '{field}' es un error tipográfico, corrígelo. Si son metadatos intencionales del desarrollador, muévelos dentro del objeto 'metadata'.",
415
+ "MODULE_MANIFEST_NAME_MISMATCH": "El manifiesto del módulo para el paquete '{packageName}' declara el nombre '{manifestName}' que no coincide con el nombre en package.json '{packageName}'.",
416
+ "HINT_MODULE_MANIFEST_NAME_MISMATCH": "El campo 'name' en slothlet.module.json es opcional pero debe coincidir con package.json cuando está presente. Elimina el campo 'name' del manifiesto o alinéalo con package.json.",
417
+ "MODULE_MANIFEST_VERSION_MISMATCH": "El manifiesto del módulo para el paquete '{packageName}' declara la versión '{manifestVersion}' que no coincide con la versión en package.json '{packageVersion}'.",
418
+ "HINT_MODULE_MANIFEST_VERSION_MISMATCH": "El campo 'version' en slothlet.module.json es opcional pero debe coincidir con package.json cuando está presente. Elimina el campo 'version' del manifiesto o alinéalo con package.json.",
419
+ "MODULE_PATH_TRAVERSAL": "El paquete de módulo '{packageName}' tiene apiDir '{apiDir}' que se resuelve fuera de la raíz del paquete ('{resolvedPath}' no está bajo '{packageRoot}').",
420
+ "HINT_MODULE_PATH_TRAVERSAL": "apiDir debe ser una ruta relativa que permanezca dentro del paquete. Elimina los segmentos '..' y rutas absolutas.",
421
+ "MODULE_VERSION_UNSUPPORTED": "El manifiesto del módulo para el paquete '{packageName}' utiliza una schemaVersion no compatible '{schemaVersion}'. Compatibles: 1.",
422
+ "HINT_MODULE_VERSION_UNSUPPORTED": "Actualiza slothlet a una versión que sea compatible con schemaVersion {schemaVersion}, o actualiza el manifiesto para usar schemaVersion 1.",
423
+ "MODULE_PACKAGE_NOT_FOUND": "Paquete de módulo '{packageName}' no encontrado en la caché de descubrimiento. {hint}",
424
+ "HINT_MODULE_PACKAGE_NOT_FOUND": "El paquete no está instalado, scanRoot no cubre su ubicación, o aún no se ha realizado una llamada a discover(). addModule(name) ejecutará discover() de forma diferida con scanRoot=process.cwd() si la caché está vacía.",
425
+ "MODULE_RESERVED_MOUNTPATH": "El paquete de módulo '{packageName}' declara la raíz reservada de mountPath '{mountPathRoot}' (de mountPath '{mountPath}'). Raíces reservadas: slothlet, shutdown, destroy.",
426
+ "HINT_MODULE_RESERVED_MOUNTPATH": "Las raíces reservadas de mountPath son utilizadas por la superficie integrada de slothlet y no pueden ser reclamadas por módulos. Elige un segmento raíz diferente.",
427
+ "MODULE_DUPLICATE_NAME_VERSION_MISMATCH": "El paquete de módulo '{packageName}' versión '{version}' se encontró en múltiples rutas reales distintas: {paths}.",
428
+ "HINT_MODULE_DUPLICATE_NAME_VERSION_MISMATCH": "Esto es casi con certeza una configuración incorrecta — el mismo paquete en la misma versión instalado en dos ubicaciones no relacionadas. Investiga cuál instalación es la deseada y elimina la otra.",
429
+ "MODULE_MOUNT_COLLISION": "El paquete de módulo '{packageName}' no se puede montar en '{mountPath}': la ruta ya está ocupada por el módulo '{existingModuleID}' y collisionMode es '{collisionMode}'.",
430
+ "HINT_MODULE_MOUNT_COLLISION": "Cambia el mountPath del manifiesto, pasa `collisionMode: \"merge\"` a `addModule()` / `addModules()` para permitir la coexistencia, o llama primero a `removeModule()` en el módulo existente."
409
431
  },
410
432
  "metadata": {
411
433
  "code": "es-mx",
@@ -405,7 +405,29 @@
405
405
  "PERM_RULE_CONDITION_INVALID": "rule.condition doit être un objet simple, une fonction, ou un tableau dont chaque entrée est un objet simple ou une fonction",
406
406
  "BRACE_EXPANSION_MAX_DEPTH": "L'expansion des accolades a dépassé la profondeur maximale de {maxDepth}",
407
407
  "HINT_BRACE_EXPANSION_MAX_DEPTH": "Réduisez le niveau d'imbrication dans vos motifs d'accolades ou augmentez l'option maxDepth.",
408
- "PERMISSION_MANAGER_NOT_AVAILABLE": "Le gestionnaire de permissions n'est pas disponible dans cette instance Slothlet"
408
+ "PERMISSION_MANAGER_NOT_AVAILABLE": "Le gestionnaire de permissions n'est pas disponible dans cette instance Slothlet",
409
+ "MODULE_MANIFEST_NOT_FOUND": "Manifeste de module introuvable pour le paquet '{packageName}' : '{manifestPath}' attendu mais le fichier n'existe pas.",
410
+ "HINT_MODULE_MANIFEST_NOT_FOUND": "Les modules plugin doivent fournir un 'slothlet.module.json' à la racine du paquet (ou le fichier/clé spécifié via la surcharge `manifest`). Vérifiez que le fichier existe dans le paquet et que le localisateur de surcharge est correct.",
411
+ "MODULE_MANIFEST_INVALID": "Le manifeste de module est invalide pour le paquet '{packageName}' à '{manifestPath}' : {reason}.",
412
+ "HINT_MODULE_MANIFEST_INVALID": "Ouvrez le manifeste et assurez-vous qu'il est en JSON valide et correspond au schéma de module slothlet (voir schemas/slothlet.module.schema.json). Champs requis : schemaVersion, mountPath, apiDir.",
413
+ "MODULE_MANIFEST_UNKNOWN_FIELD": "Le manifeste de module pour le paquet '{packageName}' contient un champ de niveau supérieur non reconnu '{field}'. Les champs inconnus sont rejetés ; utilisez le bloc 'metadata' dédié pour les ajouts définis par le développeur.",
414
+ "HINT_MODULE_MANIFEST_UNKNOWN_FIELD": "Si '{field}' est une faute de frappe, corrigez-la. S'il s'agit de métadonnées intentionnelles du développeur, déplacez-les dans l'objet 'metadata'.",
415
+ "MODULE_MANIFEST_NAME_MISMATCH": "Le manifeste de module pour le paquet '{packageName}' déclare le nom '{manifestName}' qui ne correspond pas au nom dans package.json '{packageName}'.",
416
+ "HINT_MODULE_MANIFEST_NAME_MISMATCH": "Le champ 'name' dans slothlet.module.json est optionnel mais doit correspondre à package.json lorsqu'il est présent. Supprimez le champ 'name' du manifeste ou alignez-le avec package.json.",
417
+ "MODULE_MANIFEST_VERSION_MISMATCH": "Le manifeste de module pour le paquet '{packageName}' déclare la version '{manifestVersion}' qui ne correspond pas à la version dans package.json '{packageVersion}'.",
418
+ "HINT_MODULE_MANIFEST_VERSION_MISMATCH": "Le champ 'version' dans slothlet.module.json est optionnel mais doit correspondre à package.json lorsqu'il est présent. Supprimez le champ 'version' du manifeste ou alignez-le avec package.json.",
419
+ "MODULE_PATH_TRAVERSAL": "Le paquet de module '{packageName}' a un apiDir '{apiDir}' qui se résout en dehors de la racine du paquet ('{resolvedPath}' n'est pas sous '{packageRoot}').",
420
+ "HINT_MODULE_PATH_TRAVERSAL": "apiDir doit être un chemin relatif qui reste à l'intérieur du paquet. Supprimez les segments '..' et les chemins absolus.",
421
+ "MODULE_VERSION_UNSUPPORTED": "Le manifeste de module pour le paquet '{packageName}' utilise une schemaVersion non prise en charge '{schemaVersion}'. Prises en charge : 1.",
422
+ "HINT_MODULE_VERSION_UNSUPPORTED": "Mettez à jour slothlet vers une version prenant en charge schemaVersion {schemaVersion}, ou mettez à jour le manifeste pour utiliser schemaVersion 1.",
423
+ "MODULE_PACKAGE_NOT_FOUND": "Paquet de module '{packageName}' introuvable dans le cache de découverte. {hint}",
424
+ "HINT_MODULE_PACKAGE_NOT_FOUND": "Soit le paquet n'est pas installé, soit scanRoot ne couvre pas son emplacement, soit aucun appel à discover() n'a encore été effectué. addModule(name) exécutera discover() de manière paresseuse avec scanRoot=process.cwd() si le cache est vide.",
425
+ "MODULE_RESERVED_MOUNTPATH": "Le paquet de module '{packageName}' déclare une racine mountPath réservée '{mountPathRoot}' (à partir de mountPath '{mountPath}'). Racines réservées : slothlet, shutdown, destroy.",
426
+ "HINT_MODULE_RESERVED_MOUNTPATH": "Les racines mountPath réservées sont utilisées par la surface intégrée de slothlet et ne peuvent pas être revendiquées par des modules. Choisissez un segment racine différent.",
427
+ "MODULE_DUPLICATE_NAME_VERSION_MISMATCH": "Le paquet de module '{packageName}' version '{version}' a été trouvé à plusieurs chemins réels distincts : {paths}.",
428
+ "HINT_MODULE_DUPLICATE_NAME_VERSION_MISMATCH": "Il s'agit presque certainement d'une mauvaise configuration — le même paquet à la même version installé dans deux emplacements non liés. Identifiez quelle installation est prévue et supprimez l'autre.",
429
+ "MODULE_MOUNT_COLLISION": "Le paquet de module '{packageName}' ne peut pas être monté à '{mountPath}' : le chemin est déjà occupé par le module '{existingModuleID}' et collisionMode est '{collisionMode}'.",
430
+ "HINT_MODULE_MOUNT_COLLISION": "Changez le mountPath du manifeste, passez `collisionMode: \"merge\"` à `addModule()` / `addModules()` pour permettre la coexistence, ou appelez d'abord `removeModule()` sur le module existant."
409
431
  },
410
432
  "metadata": {
411
433
  "code": "fr-fr",
@@ -405,7 +405,29 @@
405
405
  "PERM_RULE_CONDITION_INVALID": "rule.condition एक साधारण ऑब्जेक्ट, एक फ़ंक्शन, या ऐसा ऐरे होना चाहिए जिसमें प्रत्येक प्रविष्टि एक साधारण ऑब्जेक्ट या फ़ंक्शन हो",
406
406
  "BRACE_EXPANSION_MAX_DEPTH": "ब्रेस विस्तार ने {maxDepth} की अधिकतम गहराई पार कर दी",
407
407
  "HINT_BRACE_EXPANSION_MAX_DEPTH": "अपने ब्रेस पैटर्न में नेस्टिंग कम करें या maxDepth विकल्प बढ़ाएं।",
408
- "PERMISSION_MANAGER_NOT_AVAILABLE": "अनुमति प्रबंधक इस Slothlet इंस्टेंस में उपलब्ध नहीं है"
408
+ "PERMISSION_MANAGER_NOT_AVAILABLE": "अनुमति प्रबंधक इस Slothlet इंस्टेंस में उपलब्ध नहीं है",
409
+ "MODULE_MANIFEST_NOT_FOUND": "पैकेज '{packageName}' के लिए मॉड्यूल मेनिफ़ेस्ट नहीं मिला: '{manifestPath}' की अपेक्षा थी लेकिन फ़ाइल मौजूद नहीं है।",
410
+ "HINT_MODULE_MANIFEST_NOT_FOUND": "प्लगइन मॉड्यूल को पैकेज रूट पर 'slothlet.module.json' (या `manifest` ओवरराइड के माध्यम से निर्दिष्ट फ़ाइल/कुंजी) शिप करना चाहिए। जाँचें कि फ़ाइल पैकेज में मौजूद है और ओवरराइड लोकेटर सही है।",
411
+ "MODULE_MANIFEST_INVALID": "पैकेज '{packageName}' के लिए मॉड्यूल मेनिफ़ेस्ट '{manifestPath}' पर अमान्य है: {reason}।",
412
+ "HINT_MODULE_MANIFEST_INVALID": "मेनिफ़ेस्ट खोलें और सुनिश्चित करें कि यह वैध JSON है और slothlet मॉड्यूल स्कीमा से मेल खाता है (schemas/slothlet.module.schema.json देखें)। आवश्यक फ़ील्ड: schemaVersion, mountPath, apiDir।",
413
+ "MODULE_MANIFEST_UNKNOWN_FIELD": "पैकेज '{packageName}' के लिए मॉड्यूल मेनिफ़ेस्ट में अपरिचित शीर्ष-स्तरीय फ़ील्ड '{field}' है। अज्ञात फ़ील्ड अस्वीकार किए जाते हैं; डेवलपर-परिभाषित अतिरिक्त के लिए समर्पित 'metadata' ब्लॉक का उपयोग करें।",
414
+ "HINT_MODULE_MANIFEST_UNKNOWN_FIELD": "यदि '{field}' टाइपो है, तो इसे ठीक करें। यदि यह जानबूझकर डेवलपर मेटाडेटा है, तो इसे 'metadata' ऑब्जेक्ट के अंदर ले जाएँ।",
415
+ "MODULE_MANIFEST_NAME_MISMATCH": "पैकेज '{packageName}' के लिए मॉड्यूल मेनिफ़ेस्ट नाम '{manifestName}' घोषित करता है जो package.json के नाम '{packageName}' से मेल नहीं खाता।",
416
+ "HINT_MODULE_MANIFEST_NAME_MISMATCH": "slothlet.module.json में 'name' फ़ील्ड वैकल्पिक है लेकिन उपस्थित होने पर package.json से मेल खाना चाहिए। या तो मेनिफ़ेस्ट 'name' फ़ील्ड हटाएँ या इसे package.json के साथ संरेखित करें।",
417
+ "MODULE_MANIFEST_VERSION_MISMATCH": "पैकेज '{packageName}' के लिए मॉड्यूल मेनिफ़ेस्ट संस्करण '{manifestVersion}' घोषित करता है जो package.json के संस्करण '{packageVersion}' से मेल नहीं खाता।",
418
+ "HINT_MODULE_MANIFEST_VERSION_MISMATCH": "slothlet.module.json में 'version' फ़ील्ड वैकल्पिक है लेकिन उपस्थित होने पर package.json से मेल खाना चाहिए। या तो मेनिफ़ेस्ट 'version' फ़ील्ड हटाएँ या इसे package.json के साथ संरेखित करें।",
419
+ "MODULE_PATH_TRAVERSAL": "मॉड्यूल पैकेज '{packageName}' में apiDir '{apiDir}' है जो पैकेज रूट के बाहर हल होता है ('{resolvedPath}' '{packageRoot}' के अंतर्गत नहीं है)।",
420
+ "HINT_MODULE_PATH_TRAVERSAL": "apiDir एक सापेक्ष पथ होना चाहिए जो पैकेज के अंदर रहे। '..' खंड और निरपेक्ष पथ हटाएँ।",
421
+ "MODULE_VERSION_UNSUPPORTED": "पैकेज '{packageName}' के लिए मॉड्यूल मेनिफ़ेस्ट असमर्थित schemaVersion '{schemaVersion}' का उपयोग करता है। समर्थित: 1।",
422
+ "HINT_MODULE_VERSION_UNSUPPORTED": "schemaVersion {schemaVersion} का समर्थन करने वाले संस्करण में slothlet अपग्रेड करें, या मेनिफ़ेस्ट को schemaVersion 1 का उपयोग करने के लिए अपडेट करें।",
423
+ "MODULE_PACKAGE_NOT_FOUND": "मॉड्यूल पैकेज '{packageName}' खोज कैश में नहीं मिला। {hint}",
424
+ "HINT_MODULE_PACKAGE_NOT_FOUND": "या तो पैकेज स्थापित नहीं है, scanRoot इसके स्थान को कवर नहीं करता है, या अभी तक कोई discover() कॉल नहीं किया गया है। यदि कैश खाली है, तो addModule(name) scanRoot=process.cwd() के साथ discover() को आलसी रूप से चलाएगा।",
425
+ "MODULE_RESERVED_MOUNTPATH": "मॉड्यूल पैकेज '{packageName}' आरक्षित mountPath रूट '{mountPathRoot}' घोषित करता है (mountPath '{mountPath}' से)। आरक्षित रूट: slothlet, shutdown, destroy।",
426
+ "HINT_MODULE_RESERVED_MOUNTPATH": "आरक्षित mountPath रूट slothlet के अंतर्निहित सतह द्वारा उपयोग किए जाते हैं और मॉड्यूल द्वारा दावा नहीं किए जा सकते। एक अलग रूट खंड चुनें।",
427
+ "MODULE_DUPLICATE_NAME_VERSION_MISMATCH": "मॉड्यूल पैकेज '{packageName}' संस्करण '{version}' कई अलग-अलग वास्तविक पथों पर मिला: {paths}।",
428
+ "HINT_MODULE_DUPLICATE_NAME_VERSION_MISMATCH": "यह लगभग निश्चित रूप से गलत कॉन्फ़िगरेशन है — एक ही संस्करण का एक ही पैकेज दो असंबंधित स्थानों पर स्थापित है। जाँच करें कि कौन सा इंस्टॉल इरादा है और दूसरे को हटा दें।",
429
+ "MODULE_MOUNT_COLLISION": "मॉड्यूल पैकेज '{packageName}' को '{mountPath}' पर माउंट नहीं किया जा सकता: पथ पहले से ही मॉड्यूल '{existingModuleID}' द्वारा अधिकृत है और collisionMode '{collisionMode}' है।",
430
+ "HINT_MODULE_MOUNT_COLLISION": "मेनिफ़ेस्ट का mountPath बदलें, सह-अस्तित्व की अनुमति देने के लिए `addModule()` / `addModules()` में `collisionMode: \"merge\"` पास करें, या पहले मौजूदा मॉड्यूल पर `removeModule()` कॉल करें।"
409
431
  },
410
432
  "metadata": {
411
433
  "code": "hi-in",
@@ -405,7 +405,29 @@
405
405
  "PERM_RULE_CONDITION_INVALID": "rule.condition はプレーンオブジェクト、関数、または各要素がプレーンオブジェクトか関数である配列である必要があります",
406
406
  "BRACE_EXPANSION_MAX_DEPTH": "ブレース展開が最大深度 {maxDepth} を超えました",
407
407
  "HINT_BRACE_EXPANSION_MAX_DEPTH": "波括弧パターンのネストを減らすか、maxDepthオプションを増やしてください。",
408
- "PERMISSION_MANAGER_NOT_AVAILABLE": "権限マネージャーはこのSlothletインスタンスで利用できません"
408
+ "PERMISSION_MANAGER_NOT_AVAILABLE": "権限マネージャーはこのSlothletインスタンスで利用できません",
409
+ "MODULE_MANIFEST_NOT_FOUND": "パッケージ '{packageName}' のモジュールマニフェストが見つかりません: '{manifestPath}' を期待しましたが、ファイルが存在しません。",
410
+ "HINT_MODULE_MANIFEST_NOT_FOUND": "プラグインモジュールはパッケージのルートに 'slothlet.module.json'(または `manifest` オーバーライドで指定されたファイル/キー)を含める必要があります。ファイルがパッケージ内に存在し、オーバーライドロケーターが正しいか確認してください。",
411
+ "MODULE_MANIFEST_INVALID": "パッケージ '{packageName}' のモジュールマニフェストが '{manifestPath}' で無効です: {reason}。",
412
+ "HINT_MODULE_MANIFEST_INVALID": "マニフェストを開き、有効なJSONであり、slothletモジュールスキーマ(schemas/slothlet.module.schema.json を参照)に一致することを確認してください。必須フィールド: schemaVersion、mountPath、apiDir。",
413
+ "MODULE_MANIFEST_UNKNOWN_FIELD": "パッケージ '{packageName}' のモジュールマニフェストに認識されないトップレベルフィールド '{field}' が含まれています。未知のフィールドは拒否されます。開発者定義の追加項目には専用の 'metadata' ブロックを使用してください。",
414
+ "HINT_MODULE_MANIFEST_UNKNOWN_FIELD": "'{field}' がタイプミスの場合は修正してください。意図的な開発者メタデータの場合は、'metadata' オブジェクト内に移動してください。",
415
+ "MODULE_MANIFEST_NAME_MISMATCH": "パッケージ '{packageName}' のモジュールマニフェストは名前 '{manifestName}' を宣言していますが、package.json の名前 '{packageName}' と一致しません。",
416
+ "HINT_MODULE_MANIFEST_NAME_MISMATCH": "slothlet.module.json の 'name' フィールドはオプションですが、存在する場合は package.json と一致する必要があります。マニフェストの 'name' フィールドを削除するか、package.json と揃えてください。",
417
+ "MODULE_MANIFEST_VERSION_MISMATCH": "パッケージ '{packageName}' のモジュールマニフェストはバージョン '{manifestVersion}' を宣言していますが、package.json のバージョン '{packageVersion}' と一致しません。",
418
+ "HINT_MODULE_MANIFEST_VERSION_MISMATCH": "slothlet.module.json の 'version' フィールドはオプションですが、存在する場合は package.json と一致する必要があります。マニフェストの 'version' フィールドを削除するか、package.json と揃えてください。",
419
+ "MODULE_PATH_TRAVERSAL": "モジュールパッケージ '{packageName}' の apiDir '{apiDir}' がパッケージルートの外側に解決されます('{resolvedPath}' は '{packageRoot}' の下にありません)。",
420
+ "HINT_MODULE_PATH_TRAVERSAL": "apiDir はパッケージ内に留まる相対パスである必要があります。'..' セグメントと絶対パスを削除してください。",
421
+ "MODULE_VERSION_UNSUPPORTED": "パッケージ '{packageName}' のモジュールマニフェストはサポートされていない schemaVersion '{schemaVersion}' を使用しています。サポートされているバージョン: 1。",
422
+ "HINT_MODULE_VERSION_UNSUPPORTED": "schemaVersion {schemaVersion} をサポートするバージョンに slothlet を更新するか、マニフェストを schemaVersion 1 を使用するように更新してください。",
423
+ "MODULE_PACKAGE_NOT_FOUND": "モジュールパッケージ '{packageName}' は検出キャッシュ内で見つかりませんでした。{hint}",
424
+ "HINT_MODULE_PACKAGE_NOT_FOUND": "パッケージがインストールされていない、scanRoot がその場所をカバーしていない、または discover() の呼び出しがまだ行われていない可能性があります。キャッシュが空の場合、addModule(name) は scanRoot=process.cwd() で discover() を遅延実行します。",
425
+ "MODULE_RESERVED_MOUNTPATH": "モジュールパッケージ '{packageName}' は予約された mountPath ルート '{mountPathRoot}'(mountPath '{mountPath}' から)を宣言しています。予約されたルート: slothlet、shutdown、destroy。",
426
+ "HINT_MODULE_RESERVED_MOUNTPATH": "予約された mountPath ルートは slothlet の組み込みサーフェスで使用されており、モジュールが請求することはできません。別のルートセグメントを選択してください。",
427
+ "MODULE_DUPLICATE_NAME_VERSION_MISMATCH": "モジュールパッケージ '{packageName}' バージョン '{version}' が複数の異なる実パスで見つかりました: {paths}。",
428
+ "HINT_MODULE_DUPLICATE_NAME_VERSION_MISMATCH": "これはほぼ確実に設定ミスです — 同じパッケージの同じバージョンが2つの無関係な場所にインストールされています。どのインストールが意図されたものかを調査し、もう一方を削除してください。",
429
+ "MODULE_MOUNT_COLLISION": "モジュールパッケージ '{packageName}' は '{mountPath}' にマウントできません: パスは既にモジュール '{existingModuleID}' によって占有されており、collisionMode は '{collisionMode}' です。",
430
+ "HINT_MODULE_MOUNT_COLLISION": "マニフェストの mountPath を変更するか、共存を許可するために `addModule()` / `addModules()` に `collisionMode: \"merge\"` を渡すか、まず既存のモジュールに対して `removeModule()` を呼び出してください。"
409
431
  },
410
432
  "metadata": {
411
433
  "code": "ja-jp",
@@ -405,7 +405,29 @@
405
405
  "PERM_RULE_CONDITION_INVALID": "rule.condition은 일반 객체, 함수 또는 각 항목이 일반 객체 또는 함수인 배열이어야 합니다",
406
406
  "BRACE_EXPANSION_MAX_DEPTH": "중괄호 확장이 최대 깊이 {maxDepth}을(를) 초과했습니다",
407
407
  "HINT_BRACE_EXPANSION_MAX_DEPTH": "중괄호 패턴의 중첩을 줄이거나 maxDepth 옵션을 늘리세요.",
408
- "PERMISSION_MANAGER_NOT_AVAILABLE": "권한 관리자가 이 Slothlet 인스턴스에서 사용할 수 없습니다"
408
+ "PERMISSION_MANAGER_NOT_AVAILABLE": "권한 관리자가 이 Slothlet 인스턴스에서 사용할 수 없습니다",
409
+ "MODULE_MANIFEST_NOT_FOUND": "패키지 '{packageName}'의 모듈 매니페스트를 찾을 수 없습니다: '{manifestPath}'를 예상했지만 파일이 존재하지 않습니다.",
410
+ "HINT_MODULE_MANIFEST_NOT_FOUND": "플러그인 모듈은 패키지 루트에 'slothlet.module.json'(또는 `manifest` 재정의로 지정된 파일/키)을 포함해야 합니다. 파일이 패키지에 존재하고 재정의 로케이터가 올바른지 확인하세요.",
411
+ "MODULE_MANIFEST_INVALID": "패키지 '{packageName}'의 모듈 매니페스트가 '{manifestPath}'에서 유효하지 않습니다: {reason}.",
412
+ "HINT_MODULE_MANIFEST_INVALID": "매니페스트를 열고 유효한 JSON이며 slothlet 모듈 스키마(schemas/slothlet.module.schema.json 참조)와 일치하는지 확인하세요. 필수 필드: schemaVersion, mountPath, apiDir.",
413
+ "MODULE_MANIFEST_UNKNOWN_FIELD": "패키지 '{packageName}'의 모듈 매니페스트에 인식되지 않은 최상위 필드 '{field}'가 포함되어 있습니다. 알 수 없는 필드는 거부됩니다. 개발자 정의 추가 항목에는 전용 'metadata' 블록을 사용하세요.",
414
+ "HINT_MODULE_MANIFEST_UNKNOWN_FIELD": "'{field}'가 오타인 경우 수정하세요. 의도적인 개발자 메타데이터인 경우 'metadata' 객체 안으로 이동하세요.",
415
+ "MODULE_MANIFEST_NAME_MISMATCH": "패키지 '{packageName}'의 모듈 매니페스트가 이름 '{manifestName}'을(를) 선언하는데 package.json의 이름 '{packageName}'과(와) 일치하지 않습니다.",
416
+ "HINT_MODULE_MANIFEST_NAME_MISMATCH": "slothlet.module.json의 'name' 필드는 선택 사항이지만 존재하는 경우 package.json과 일치해야 합니다. 매니페스트의 'name' 필드를 제거하거나 package.json과 맞추세요.",
417
+ "MODULE_MANIFEST_VERSION_MISMATCH": "패키지 '{packageName}'의 모듈 매니페스트가 버전 '{manifestVersion}'을(를) 선언하는데 package.json의 버전 '{packageVersion}'과(와) 일치하지 않습니다.",
418
+ "HINT_MODULE_MANIFEST_VERSION_MISMATCH": "slothlet.module.json의 'version' 필드는 선택 사항이지만 존재하는 경우 package.json과 일치해야 합니다. 매니페스트의 'version' 필드를 제거하거나 package.json과 맞추세요.",
419
+ "MODULE_PATH_TRAVERSAL": "모듈 패키지 '{packageName}'의 apiDir '{apiDir}'이(가) 패키지 루트 외부로 해석됩니다('{resolvedPath}'은(는) '{packageRoot}' 아래에 없음).",
420
+ "HINT_MODULE_PATH_TRAVERSAL": "apiDir은 패키지 내부에 머무르는 상대 경로여야 합니다. '..' 세그먼트와 절대 경로를 제거하세요.",
421
+ "MODULE_VERSION_UNSUPPORTED": "패키지 '{packageName}'의 모듈 매니페스트가 지원되지 않는 schemaVersion '{schemaVersion}'을(를) 사용합니다. 지원: 1.",
422
+ "HINT_MODULE_VERSION_UNSUPPORTED": "schemaVersion {schemaVersion}을(를) 지원하는 버전으로 slothlet을 업그레이드하거나 매니페스트를 schemaVersion 1을 사용하도록 업데이트하세요.",
423
+ "MODULE_PACKAGE_NOT_FOUND": "모듈 패키지 '{packageName}'을(를) 검색 캐시에서 찾을 수 없습니다. {hint}",
424
+ "HINT_MODULE_PACKAGE_NOT_FOUND": "패키지가 설치되지 않았거나, scanRoot가 해당 위치를 포함하지 않거나, 아직 discover() 호출이 이루어지지 않았습니다. 캐시가 비어 있으면 addModule(name)이 scanRoot=process.cwd()로 discover()를 지연 실행합니다.",
425
+ "MODULE_RESERVED_MOUNTPATH": "모듈 패키지 '{packageName}'이(가) 예약된 mountPath 루트 '{mountPathRoot}'(mountPath '{mountPath}'에서)을(를) 선언합니다. 예약된 루트: slothlet, shutdown, destroy.",
426
+ "HINT_MODULE_RESERVED_MOUNTPATH": "예약된 mountPath 루트는 slothlet의 내장 표면에서 사용되며 모듈이 청구할 수 없습니다. 다른 루트 세그먼트를 선택하세요.",
427
+ "MODULE_DUPLICATE_NAME_VERSION_MISMATCH": "모듈 패키지 '{packageName}' 버전 '{version}'이(가) 여러 개의 다른 실제 경로에서 발견되었습니다: {paths}.",
428
+ "HINT_MODULE_DUPLICATE_NAME_VERSION_MISMATCH": "이것은 거의 확실히 잘못된 구성입니다 — 동일한 버전의 동일한 패키지가 관련 없는 두 위치에 설치되어 있습니다. 어떤 설치가 의도된 것인지 조사하고 다른 것을 제거하세요.",
429
+ "MODULE_MOUNT_COLLISION": "모듈 패키지 '{packageName}'을(를) '{mountPath}'에 마운트할 수 없습니다: 경로가 이미 모듈 '{existingModuleID}'에 의해 점유되어 있고 collisionMode는 '{collisionMode}'입니다.",
430
+ "HINT_MODULE_MOUNT_COLLISION": "매니페스트의 mountPath를 변경하거나, 공존을 허용하기 위해 `addModule()` / `addModules()`에 `collisionMode: \"merge\"`를 전달하거나, 먼저 기존 모듈에서 `removeModule()`을 호출하세요."
409
431
  },
410
432
  "metadata": {
411
433
  "code": "ko-kr",
@@ -405,7 +405,29 @@
405
405
  "PERM_RULE_CONDITION_INVALID": "rule.condition deve ser um objeto simples, uma função ou um array em que cada entrada seja um objeto simples ou uma função",
406
406
  "BRACE_EXPANSION_MAX_DEPTH": "Expansão de chaves excedeu a profundidade máxima de {maxDepth}",
407
407
  "HINT_BRACE_EXPANSION_MAX_DEPTH": "Reduza o aninhamento em seus padrões de chaves ou aumente a opção maxDepth.",
408
- "PERMISSION_MANAGER_NOT_AVAILABLE": "Gerenciador de permissões não está disponível nesta instância do Slothlet"
408
+ "PERMISSION_MANAGER_NOT_AVAILABLE": "Gerenciador de permissões não está disponível nesta instância do Slothlet",
409
+ "MODULE_MANIFEST_NOT_FOUND": "Manifesto do módulo não encontrado para o pacote '{packageName}': '{manifestPath}' esperado, mas o arquivo não existe.",
410
+ "HINT_MODULE_MANIFEST_NOT_FOUND": "Os módulos plugin devem incluir um 'slothlet.module.json' na raiz do pacote (ou o arquivo/chave especificado via a sobrescrita `manifest`). Verifique se o arquivo existe no pacote e se o localizador de sobrescrita está correto.",
411
+ "MODULE_MANIFEST_INVALID": "O manifesto do módulo é inválido para o pacote '{packageName}' em '{manifestPath}': {reason}.",
412
+ "HINT_MODULE_MANIFEST_INVALID": "Abra o manifesto e certifique-se de que seja JSON válido e corresponda ao esquema do módulo slothlet (veja schemas/slothlet.module.schema.json). Campos obrigatórios: schemaVersion, mountPath, apiDir.",
413
+ "MODULE_MANIFEST_UNKNOWN_FIELD": "O manifesto do módulo para o pacote '{packageName}' contém um campo de nível superior não reconhecido '{field}'. Campos desconhecidos são rejeitados; use o bloco 'metadata' dedicado para extras definidos pelo desenvolvedor.",
414
+ "HINT_MODULE_MANIFEST_UNKNOWN_FIELD": "Se '{field}' é um erro de digitação, corrija-o. Se forem metadados intencionais do desenvolvedor, mova-os para dentro do objeto 'metadata'.",
415
+ "MODULE_MANIFEST_NAME_MISMATCH": "O manifesto do módulo para o pacote '{packageName}' declara o nome '{manifestName}' que não corresponde ao nome em package.json '{packageName}'.",
416
+ "HINT_MODULE_MANIFEST_NAME_MISMATCH": "O campo 'name' em slothlet.module.json é opcional, mas deve corresponder a package.json quando presente. Remova o campo 'name' do manifesto ou alinhe-o com package.json.",
417
+ "MODULE_MANIFEST_VERSION_MISMATCH": "O manifesto do módulo para o pacote '{packageName}' declara a versão '{manifestVersion}' que não corresponde à versão em package.json '{packageVersion}'.",
418
+ "HINT_MODULE_MANIFEST_VERSION_MISMATCH": "O campo 'version' em slothlet.module.json é opcional, mas deve corresponder a package.json quando presente. Remova o campo 'version' do manifesto ou alinhe-o com package.json.",
419
+ "MODULE_PATH_TRAVERSAL": "O pacote de módulo '{packageName}' tem apiDir '{apiDir}' que se resolve fora da raiz do pacote ('{resolvedPath}' não está sob '{packageRoot}').",
420
+ "HINT_MODULE_PATH_TRAVERSAL": "apiDir deve ser um caminho relativo que permaneça dentro do pacote. Remova os segmentos '..' e caminhos absolutos.",
421
+ "MODULE_VERSION_UNSUPPORTED": "O manifesto do módulo para o pacote '{packageName}' usa uma schemaVersion não suportada '{schemaVersion}'. Suportadas: 1.",
422
+ "HINT_MODULE_VERSION_UNSUPPORTED": "Atualize o slothlet para uma versão que suporte schemaVersion {schemaVersion}, ou atualize o manifesto para usar schemaVersion 1.",
423
+ "MODULE_PACKAGE_NOT_FOUND": "Pacote de módulo '{packageName}' não encontrado no cache de descoberta. {hint}",
424
+ "HINT_MODULE_PACKAGE_NOT_FOUND": "Ou o pacote não está instalado, ou scanRoot não cobre sua localização, ou nenhuma chamada a discover() foi feita ainda. addModule(name) executará discover() de forma preguiçosa com scanRoot=process.cwd() se o cache estiver vazio.",
425
+ "MODULE_RESERVED_MOUNTPATH": "O pacote de módulo '{packageName}' declara a raiz reservada de mountPath '{mountPathRoot}' (a partir de mountPath '{mountPath}'). Raízes reservadas: slothlet, shutdown, destroy.",
426
+ "HINT_MODULE_RESERVED_MOUNTPATH": "As raízes reservadas de mountPath são usadas pela superfície integrada do slothlet e não podem ser reivindicadas por módulos. Escolha um segmento raiz diferente.",
427
+ "MODULE_DUPLICATE_NAME_VERSION_MISMATCH": "O pacote de módulo '{packageName}' versão '{version}' foi encontrado em vários caminhos reais distintos: {paths}.",
428
+ "HINT_MODULE_DUPLICATE_NAME_VERSION_MISMATCH": "Isso é quase certamente uma configuração incorreta — o mesmo pacote na mesma versão instalado em dois locais não relacionados. Investigue qual instalação é a pretendida e remova a outra.",
429
+ "MODULE_MOUNT_COLLISION": "O pacote de módulo '{packageName}' não pode ser montado em '{mountPath}': o caminho já está ocupado pelo módulo '{existingModuleID}' e collisionMode é '{collisionMode}'.",
430
+ "HINT_MODULE_MOUNT_COLLISION": "Altere o mountPath do manifesto, passe `collisionMode: \"merge\"` para `addModule()` / `addModules()` para permitir a coexistência, ou chame primeiro `removeModule()` no módulo existente."
409
431
  },
410
432
  "metadata": {
411
433
  "code": "pt-br",
@@ -405,7 +405,29 @@
405
405
  "PERM_RULE_CONDITION_INVALID": "rule.condition должно быть обычным объектом, функцией или массивом, в котором каждый элемент является обычным объектом или функцией",
406
406
  "BRACE_EXPANSION_MAX_DEPTH": "Расширение фигурных скобок превысило максимальную глубину {maxDepth}",
407
407
  "HINT_BRACE_EXPANSION_MAX_DEPTH": "Уменьшите вложенность в ваших шаблонах скобок или увеличьте параметр maxDepth.",
408
- "PERMISSION_MANAGER_NOT_AVAILABLE": "Менеджер разрешений недоступен в этом экземпляре Slothlet"
408
+ "PERMISSION_MANAGER_NOT_AVAILABLE": "Менеджер разрешений недоступен в этом экземпляре Slothlet",
409
+ "MODULE_MANIFEST_NOT_FOUND": "Манифест модуля не найден для пакета '{packageName}': ожидался '{manifestPath}', но файл не существует.",
410
+ "HINT_MODULE_MANIFEST_NOT_FOUND": "Модули плагинов должны поставляться с 'slothlet.module.json' в корне пакета (или файлом/ключом, указанным через переопределение `manifest`). Проверьте, что файл существует в пакете и локатор переопределения корректен.",
411
+ "MODULE_MANIFEST_INVALID": "Манифест модуля недействителен для пакета '{packageName}' в '{manifestPath}': {reason}.",
412
+ "HINT_MODULE_MANIFEST_INVALID": "Откройте манифест и убедитесь, что это валидный JSON и он соответствует схеме модуля slothlet (см. schemas/slothlet.module.schema.json). Обязательные поля: schemaVersion, mountPath, apiDir.",
413
+ "MODULE_MANIFEST_UNKNOWN_FIELD": "Манифест модуля для пакета '{packageName}' содержит нераспознанное поле верхнего уровня '{field}'. Неизвестные поля отклоняются; используйте выделенный блок 'metadata' для определяемых разработчиком дополнений.",
414
+ "HINT_MODULE_MANIFEST_UNKNOWN_FIELD": "Если '{field}' — это опечатка, исправьте её. Если это намеренные метаданные разработчика, переместите их внутрь объекта 'metadata'.",
415
+ "MODULE_MANIFEST_NAME_MISMATCH": "Манифест модуля для пакета '{packageName}' объявляет имя '{manifestName}', которое не совпадает с именем в package.json '{packageName}'.",
416
+ "HINT_MODULE_MANIFEST_NAME_MISMATCH": "Поле 'name' в slothlet.module.json является необязательным, но должно совпадать с package.json при наличии. Либо удалите поле 'name' из манифеста, либо приведите его в соответствие с package.json.",
417
+ "MODULE_MANIFEST_VERSION_MISMATCH": "Манифест модуля для пакета '{packageName}' объявляет версию '{manifestVersion}', которая не совпадает с версией в package.json '{packageVersion}'.",
418
+ "HINT_MODULE_MANIFEST_VERSION_MISMATCH": "Поле 'version' в slothlet.module.json является необязательным, но должно совпадать с package.json при наличии. Либо удалите поле 'version' из манифеста, либо приведите его в соответствие с package.json.",
419
+ "MODULE_PATH_TRAVERSAL": "Пакет модуля '{packageName}' имеет apiDir '{apiDir}', который разрешается за пределами корня пакета ('{resolvedPath}' не находится под '{packageRoot}').",
420
+ "HINT_MODULE_PATH_TRAVERSAL": "apiDir должен быть относительным путём, остающимся внутри пакета. Удалите сегменты '..' и абсолютные пути.",
421
+ "MODULE_VERSION_UNSUPPORTED": "Манифест модуля для пакета '{packageName}' использует неподдерживаемую schemaVersion '{schemaVersion}'. Поддерживаемые: 1.",
422
+ "HINT_MODULE_VERSION_UNSUPPORTED": "Обновите slothlet до версии, поддерживающей schemaVersion {schemaVersion}, или обновите манифест для использования schemaVersion 1.",
423
+ "MODULE_PACKAGE_NOT_FOUND": "Пакет модуля '{packageName}' не найден в кэше обнаружения. {hint}",
424
+ "HINT_MODULE_PACKAGE_NOT_FOUND": "Либо пакет не установлен, либо scanRoot не охватывает его местоположение, либо вызов discover() ещё не был выполнен. addModule(name) лениво запустит discover() с scanRoot=process.cwd(), если кэш пуст.",
425
+ "MODULE_RESERVED_MOUNTPATH": "Пакет модуля '{packageName}' объявляет зарезервированный корень mountPath '{mountPathRoot}' (из mountPath '{mountPath}'). Зарезервированные корни: slothlet, shutdown, destroy.",
426
+ "HINT_MODULE_RESERVED_MOUNTPATH": "Зарезервированные корни mountPath используются встроенным интерфейсом slothlet и не могут быть востребованы модулями. Выберите другой корневой сегмент.",
427
+ "MODULE_DUPLICATE_NAME_VERSION_MISMATCH": "Пакет модуля '{packageName}' версии '{version}' был найден по нескольким различным реальным путям: {paths}.",
428
+ "HINT_MODULE_DUPLICATE_NAME_VERSION_MISMATCH": "Это почти наверняка ошибка конфигурации — один и тот же пакет той же версии установлен в двух несвязанных местах. Выясните, какая установка является целевой, и удалите другую.",
429
+ "MODULE_MOUNT_COLLISION": "Пакет модуля '{packageName}' не может быть смонтирован в '{mountPath}': путь уже занят модулем '{existingModuleID}' и collisionMode имеет значение '{collisionMode}'.",
430
+ "HINT_MODULE_MOUNT_COLLISION": "Измените mountPath в манифесте, передайте `collisionMode: \"merge\"` в `addModule()` / `addModules()` для разрешения сосуществования или сначала вызовите `removeModule()` для существующего модуля."
409
431
  },
410
432
  "metadata": {
411
433
  "code": "ru-ru",
@@ -405,7 +405,29 @@
405
405
  "PERM_RULE_CONDITION_INVALID": "rule.condition 必须是普通对象、函数,或一个数组且其中每个条目都必须是普通对象或函数",
406
406
  "BRACE_EXPANSION_MAX_DEPTH": "花括号展开超过了最大深度 {maxDepth}",
407
407
  "HINT_BRACE_EXPANSION_MAX_DEPTH": "减少括号模式中的嵌套层数或增加 maxDepth 选项。",
408
- "PERMISSION_MANAGER_NOT_AVAILABLE": "权限管理器在此 Slothlet 实例中不可用"
408
+ "PERMISSION_MANAGER_NOT_AVAILABLE": "权限管理器在此 Slothlet 实例中不可用",
409
+ "MODULE_MANIFEST_NOT_FOUND": "未找到包 '{packageName}' 的模块清单:预期为 '{manifestPath}' 但文件不存在。",
410
+ "HINT_MODULE_MANIFEST_NOT_FOUND": "插件模块必须在包根目录提供 'slothlet.module.json'(或通过 `manifest` 覆盖指定的文件/键)。请检查文件是否存在于包中以及覆盖定位符是否正确。",
411
+ "MODULE_MANIFEST_INVALID": "包 '{packageName}' 的模块清单在 '{manifestPath}' 处无效:{reason}。",
412
+ "HINT_MODULE_MANIFEST_INVALID": "打开清单并确保它是有效的 JSON 且符合 slothlet 模块架构(参见 schemas/slothlet.module.schema.json)。必需字段:schemaVersion、mountPath、apiDir。",
413
+ "MODULE_MANIFEST_UNKNOWN_FIELD": "包 '{packageName}' 的模块清单包含无法识别的顶层字段 '{field}'。未知字段将被拒绝;请使用专用的 'metadata' 块来存放开发者定义的附加项。",
414
+ "HINT_MODULE_MANIFEST_UNKNOWN_FIELD": "如果 '{field}' 是拼写错误,请修正。如果是有意的开发者元数据,请将其移到 'metadata' 对象内。",
415
+ "MODULE_MANIFEST_NAME_MISMATCH": "包 '{packageName}' 的模块清单声明名称 '{manifestName}',与 package.json 中的名称 '{packageName}' 不匹配。",
416
+ "HINT_MODULE_MANIFEST_NAME_MISMATCH": "slothlet.module.json 中的 'name' 字段是可选的,但存在时必须与 package.json 匹配。请删除清单中的 'name' 字段或使其与 package.json 一致。",
417
+ "MODULE_MANIFEST_VERSION_MISMATCH": "包 '{packageName}' 的模块清单声明版本 '{manifestVersion}',与 package.json 中的版本 '{packageVersion}' 不匹配。",
418
+ "HINT_MODULE_MANIFEST_VERSION_MISMATCH": "slothlet.module.json 中的 'version' 字段是可选的,但存在时必须与 package.json 匹配。请删除清单中的 'version' 字段或使其与 package.json 一致。",
419
+ "MODULE_PATH_TRAVERSAL": "模块包 '{packageName}' 的 apiDir '{apiDir}' 解析到包根目录之外('{resolvedPath}' 不在 '{packageRoot}' 之下)。",
420
+ "HINT_MODULE_PATH_TRAVERSAL": "apiDir 必须是保持在包内部的相对路径。请删除 '..' 段和绝对路径。",
421
+ "MODULE_VERSION_UNSUPPORTED": "包 '{packageName}' 的模块清单使用了不支持的 schemaVersion '{schemaVersion}'。支持的版本:1。",
422
+ "HINT_MODULE_VERSION_UNSUPPORTED": "将 slothlet 升级到支持 schemaVersion {schemaVersion} 的版本,或将清单更新为使用 schemaVersion 1。",
423
+ "MODULE_PACKAGE_NOT_FOUND": "在发现缓存中未找到模块包 '{packageName}'。{hint}",
424
+ "HINT_MODULE_PACKAGE_NOT_FOUND": "可能是包未安装、scanRoot 未覆盖其位置,或尚未进行 discover() 调用。如果缓存为空,addModule(name) 将使用 scanRoot=process.cwd() 延迟运行 discover()。",
425
+ "MODULE_RESERVED_MOUNTPATH": "模块包 '{packageName}' 声明了保留的 mountPath 根 '{mountPathRoot}'(来自 mountPath '{mountPath}')。保留的根:slothlet、shutdown、destroy。",
426
+ "HINT_MODULE_RESERVED_MOUNTPATH": "保留的 mountPath 根由 slothlet 的内置接口使用,模块不能占用。请选择不同的根段。",
427
+ "MODULE_DUPLICATE_NAME_VERSION_MISMATCH": "模块包 '{packageName}' 版本 '{version}' 在多个不同的真实路径中被发现:{paths}。",
428
+ "HINT_MODULE_DUPLICATE_NAME_VERSION_MISMATCH": "这几乎肯定是配置错误 — 同一版本的同一包安装在两个不相关的位置。请调查哪个安装是预期的并删除另一个。",
429
+ "MODULE_MOUNT_COLLISION": "模块包 '{packageName}' 无法挂载在 '{mountPath}':路径已被模块 '{existingModuleID}' 占用且 collisionMode 为 '{collisionMode}'。",
430
+ "HINT_MODULE_MOUNT_COLLISION": "更改清单的 mountPath,向 `addModule()` / `addModules()` 传递 `collisionMode: \"merge\"` 以允许共存,或先在现有模块上调用 `removeModule()`。"
409
431
  },
410
432
  "metadata": {
411
433
  "code": "zh-cn",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cldmv/slothlet",
3
- "version": "3.7.0",
3
+ "version": "3.8.0",
4
4
  "moduleVersions": {
5
5
  "lazy": "3.0.0",
6
6
  "eager": "3.0.0",
@@ -126,7 +126,8 @@
126
126
  },
127
127
  "types": "./types/dist/lib/i18n/translations.d.mts",
128
128
  "import": "./dist/lib/i18n/translations.mjs"
129
- }
129
+ },
130
+ "./schemas/slothlet.module.schema.json": "./schemas/slothlet.module.schema.json"
130
131
  },
131
132
  "types": "./types/index.d.mts",
132
133
  "scripts": {
@@ -281,7 +282,6 @@
281
282
  "@eslint/js": "^10.0.1",
282
283
  "@eslint/json": "^1.2.0",
283
284
  "@eslint/markdown": "^8.0.1",
284
- "@html-eslint/parser": "^0.59.0",
285
285
  "@types/node": "^25.6.0",
286
286
  "@vitest/coverage-v8": "^4.1.5",
287
287
  "acorn": "^8.16.0",
@@ -304,11 +304,6 @@
304
304
  "optionalDependencies": {
305
305
  "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.17"
306
306
  },
307
- "overrides": {
308
- "minimatch": "^10.2.3",
309
- "rollup": "^4.59.0",
310
- "rolldown": "1.0.0-rc.17"
311
- },
312
307
  "repository": {
313
308
  "type": "git",
314
309
  "url": "git+https://github.com/CLDMV/slothlet.git"
@@ -331,7 +326,8 @@
331
326
  "types/index.d.mts",
332
327
  "types/index.d.mts.map",
333
328
  "dist/",
334
- "bin/"
329
+ "bin/",
330
+ "schemas/"
335
331
  ],
336
332
  "sideEffects": false
337
333
  }
@@ -0,0 +1,98 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://cldmv.github.io/slothlet/schemas/slothlet.module.schema.json",
4
+ "title": "Slothlet Module Manifest",
5
+ "description": "Manifest shipped by a slothlet module package at `slothlet.module.json` (default) or via an override locator. Describes how the module mounts into a host slothlet api tree.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["schemaVersion", "mountPath", "apiDir"],
9
+ "properties": {
10
+ "schemaVersion": {
11
+ "description": "Manifest schema version. Only `1` is supported in this release.",
12
+ "type": "integer",
13
+ "const": 1
14
+ },
15
+ "name": {
16
+ "description": "Package identity. Optional in the manifest — slothlet always reads this from package.json. If present, MUST match package.json's `name` exactly; mismatch throws MODULE_MANIFEST_NAME_MISMATCH and the module does not load.",
17
+ "type": "string",
18
+ "minLength": 1
19
+ },
20
+ "version": {
21
+ "description": "Semver. Optional in the manifest — slothlet always reads this from package.json (and uses it for multi-version mounting via versionConfig). If present, MUST match package.json's `version` exactly; mismatch throws MODULE_MANIFEST_VERSION_MISMATCH.",
22
+ "type": "string",
23
+ "minLength": 1
24
+ },
25
+ "description": {
26
+ "description": "Free-form human-readable description. Optional. Falls back to package.json's `description` if absent. If present, overrides package.json silently (no mismatch error, unlike `name` and `version`).",
27
+ "type": "string"
28
+ },
29
+ "mountPath": {
30
+ "description": "Where the module mounts in the api tree. Same shape `api.add()` accepts. Reserved roots (`slothlet`, `shutdown`, `destroy`) are rejected with MODULE_RESERVED_MOUNTPATH.",
31
+ "oneOf": [
32
+ {
33
+ "type": "string",
34
+ "minLength": 1
35
+ },
36
+ {
37
+ "type": "array",
38
+ "items": {
39
+ "type": "string",
40
+ "minLength": 1
41
+ },
42
+ "minItems": 1
43
+ }
44
+ ]
45
+ },
46
+ "apiDir": {
47
+ "description": "Path inside the package to the slothlet folder. Resolved relative to the package root. Path traversal (resolving outside the package root) is rejected with MODULE_PATH_TRAVERSAL.",
48
+ "type": "string",
49
+ "minLength": 1
50
+ },
51
+ "kind": {
52
+ "description": "Free-form category (driver, reranker, model-provider, etc.). Slothlet stores it but does not interpret it. Hosts use it via `discover()`'s `filter` callback.",
53
+ "type": "string"
54
+ },
55
+ "priority": {
56
+ "description": "Default-comparator key for `sort()`. Higher first. Defaults to 0. Ties broken alphabetically by `packageName` (from `package.json` / `DiscoverResult.packageName`).",
57
+ "type": "number",
58
+ "default": 0
59
+ },
60
+ "dependencies": {
61
+ "description": "Other slothlet modules this module expects to be mounted. Slothlet does NOT enforce semver — values are passed through to metadata for the host to consume (e.g., a custom topological-sort comparator).",
62
+ "type": "object",
63
+ "additionalProperties": {
64
+ "type": "string"
65
+ }
66
+ },
67
+ "permissions": {
68
+ "description": "Module's declared access expectations. Integrates with slothlet's permission system (see permission-system docs). The discovery + mount pipeline reads these entries into per-module metadata under `_module.manifest.permissions` (retrievable via `api.slothlet.metadata.getFor(mountPath)._module.manifest.permissions`); the host applies them to the live permission system by passing each entry to `api.slothlet.permissions.addRule()` manually.",
69
+ "type": "array",
70
+ "items": {
71
+ "type": "object",
72
+ "additionalProperties": false,
73
+ "required": ["caller", "target", "effect"],
74
+ "properties": {
75
+ "caller": {
76
+ "description": "Glob pattern matching caller api path.",
77
+ "type": "string",
78
+ "minLength": 1
79
+ },
80
+ "target": {
81
+ "description": "Glob pattern matching target api path.",
82
+ "type": "string",
83
+ "minLength": 1
84
+ },
85
+ "effect": {
86
+ "description": "Rule effect.",
87
+ "type": "string",
88
+ "enum": ["allow", "deny"]
89
+ }
90
+ }
91
+ }
92
+ },
93
+ "metadata": {
94
+ "description": "Dedicated opt-in block for developer-defined extras. The only place arbitrary project-specific data goes — unknown top-level fields are NOT auto-passed through (they throw MODULE_MANIFEST_UNKNOWN_FIELD). Round-tripped via metadata.getFor(mountPath)._module.manifest.metadata.",
95
+ "type": "object"
96
+ }
97
+ }
98
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"api_builder.d.mts","sourceRoot":"","sources":["../../../../dist/lib/builders/api_builder.mjs"],"names":[],"mappings":"AAAm/C;IAAuC,gCAAqC;IAAsC,0CAA66C;IAAA,oDAA081B;IAAA,6CAAqR;IAAA,uFAA0vB;IAAA,sDAA8sJ;IAAA,qDAAmkB;IAAA,kDAA2Z;CAAC;8BAAnlpC,0CAA0C"}
1
+ {"version":3,"file":"api_builder.d.mts","sourceRoot":"","sources":["../../../../dist/lib/builders/api_builder.mjs"],"names":[],"mappings":"AAAm/C;IAAuC,gCAAqC;IAAsC,0CAA66C;IAAA,oDAAys6B;IAAA,6CAAqR;IAAA,uFAA0vB;IAAA,sDAA8sJ;IAAA,qDAAmkB;IAAA,kDAA2Z;CAAC;8BAAl1tC,0CAA0C"}
@@ -0,0 +1,29 @@
1
+ export class ModuleManager extends ComponentBase {
2
+ static slothletProperty: string;
3
+ discover(options?: {}): Promise<Readonly<{
4
+ packageName: any;
5
+ packageRoot: any;
6
+ mountPath: readonly any[];
7
+ apiDir: any;
8
+ manifest: any;
9
+ }>[]>;
10
+ sort(results: any, comparator: any): any[];
11
+ getDiscoveryCache(): any[];
12
+ clearDiscoveryCache(): void;
13
+ getStaleMounts(): any[];
14
+ addModule(nameOrResult: any, options?: {}): Promise<{
15
+ packageName: any;
16
+ mountPath: any;
17
+ moduleID: any;
18
+ discoverResult: any;
19
+ versionConfig: any;
20
+ }>;
21
+ addModules(items: any, options?: {}): Promise<any[] | {
22
+ mounted: any[];
23
+ failed: any[];
24
+ }>;
25
+ removeModule(name: any, opts?: {}): Promise<boolean>;
26
+ #private;
27
+ }
28
+ import { ComponentBase } from "@cldmv/slothlet/factories/component-base";
29
+ //# sourceMappingURL=module-manager.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module-manager.d.mts","sourceRoot":"","sources":["../../../../dist/lib/handlers/module-manager.mjs"],"names":[],"mappings":"AAAqS;IAA0C,gCAAwC;IAAsE;;;;;;UAA4X;IAAA,2CAAgE;IAAA,2BAAoD;IAAA,4BAA0C;IAAA,wBAA4I;IAAA;;;;;;OAAma;IAAA;;;OAAosC;IAAA,qDAAuc;;CAA43J;8BAAp/P,0CAA0C"}
@@ -1 +1 @@
1
- {"version":3,"file":"eventemitter-context.d.mts","sourceRoot":"","sources":["../../../../dist/lib/helpers/eventemitter-context.mjs"],"names":[],"mappings":"AAAu2M,qDAAmL;AAAtjB,oDAAmY;AAAroB,mDAAkQ;AAA13L,yDAA8D"}
1
+ {"version":3,"file":"eventemitter-context.d.mts","sourceRoot":"","sources":["../../../../dist/lib/helpers/eventemitter-context.mjs"],"names":[],"mappings":"AAA24P,qDAAmL;AAAtjB,oDAAmY;AAAroB,mDAAkQ;AAA95O,yDAA8D"}
@@ -0,0 +1,8 @@
1
+ export function discoverModules(options?: {}): Promise<Readonly<{
2
+ packageName: any;
3
+ packageRoot: any;
4
+ mountPath: readonly any[];
5
+ apiDir: any;
6
+ manifest: any;
7
+ }>[]>;
8
+ //# sourceMappingURL=module-discovery.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module-discovery.d.mts","sourceRoot":"","sources":["../../../../dist/lib/helpers/module-discovery.mjs"],"names":[],"mappings":"AAAoR;;;;;;MAA0nE"}
@@ -0,0 +1,14 @@
1
+ export function validateModuleManifest(manifest: any, packageContext: any): {
2
+ schemaVersion: number;
3
+ name: any;
4
+ version: any;
5
+ description: any;
6
+ mountPath: any[];
7
+ apiDir: any;
8
+ kind: any;
9
+ priority: any;
10
+ dependencies: any;
11
+ permissions: any;
12
+ metadata: any;
13
+ };
14
+ //# sourceMappingURL=module-manifest-validator.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module-manifest-validator.d.mts","sourceRoot":"","sources":["../../../../dist/lib/helpers/module-manifest-validator.mjs"],"names":[],"mappings":"AAAsa;;;;;;;;;;;;EAA0mJ"}
@@ -0,0 +1,2 @@
1
+ export function sortModules(results: any, comparator: any): any[];
2
+ //# sourceMappingURL=module-sort.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module-sort.d.mts","sourceRoot":"","sources":["../../../../dist/lib/helpers/module-sort.mjs"],"names":[],"mappings":"AAAA,kEAAkJ"}