Modules
Module source layers, manifest, and runtime APIs.
Clapilot modules are filesystem packages with a manifest, frontend entry, optional API handler, optional background workers, and optional migrations.
Source layers and precedence
Modules are discovered from three source layers (discovery logic: src/lib/module-store/local-modules.ts):
workspace:<workspace>/modules(default/app/workspace/modules)managed:<state dir>/modules— the state dir resolves fromCLAPILOTAICORE_STATE_DIR/CLAPILOT_STATE_DIR/CLAPILOT_HOME, withOPENCLAW_HOMEretained as a legacy fallbackbundled:CLAPILOT_BUNDLED_MODULES_DIR(legacyOPENCLAW_BUNDLED_MODULES_DIR), default/app/bundled-modules, plus a host-local fallback of<repo>/bundled-moduleswhile running from the repo
Discovery scans each source in parallel and keeps a five-second process-local, single-flight snapshot with direct slug/module-key maps. Install, deactivate, delete, icon, and menu-visibility mutations invalidate the snapshot immediately. The browser independently deduplicates concurrent full-inventory requests from the sidebar and active-module guard.
When the same slug exists in several layers, the effective entry is picked in this order:
- a
fixed: truebundled module always wins (and cannot be shadowed by a workspace copy) - otherwise the higher manifest version wins
- at equal versions the bundled copy is preferred, then the newer manifest mtime, then source priority
workspace > managed > bundled
Bundled install state is instance-wide: explicit overrides live in module_installs, while app_settings.module_install_policy selects minimal for new instances or legacy_all for grandfathered instances. The legacy <workspace>/.clapilot-bundled-modules.json disabled list remains synchronized and is used only when the policy is legacy_all and no DB override exists. Fixed bundled modules ignore all overrides and are always installed. Workspace and managed entries are always installed.
The minimal new-instance set is notizen, excel-canvas, word-canvas, and agents; wiki is additionally present because it is fixed. Migration 214_module_installs.sql assigns legacy_all only to instances that already contain users when the migration first runs, preserving their former effective module set.
Manifest contract
module.json example:
{
"slug": "my-module",
"name": "My Module",
"description": "What this module does",
"version": "1.0.0",
"entry": "index.html",
"renderer": "iframe",
"workers": [],
"icon": "boxes",
"categories": ["productivity", "documents"],
"hiddenInMenu": false,
"fixed": false,
"allowOutsideRead": false,
"outsideReadRoots": []
}
Validation enforces slug format, semver, safe entry path, and icon normalization.
rendererselects how/modules/<slug>renders the module:iframe(default) loads the module'sentryHTML in a sandboxed iframe;reactmaps the slug to a first-party React component insrc/components/modules/<slug>-module.tsx(wiring:src/app/(app)/modules/[slug]/page.tsx). Only known first-party slugs have React components; third-party modules use the iframe renderer.workerslists optional background worker entry files inside the module (for exampleworkers/post-publisher.mjsinsocial-media).hiddenInMenu: truekeeps the module active and routable under/modules/<slug>, but removes its automatic left-sidebar menu entry. Admins can toggle this from the local module list without uninstalling the module (POST /api/module-store/set-menu-visibility).fixed: trueis reserved for Clapilot-owned bundled modules that must always be active (currentlywiki). Fixed bundled modules are read from the bundled source, cannot be installed into the workspace copy, cannot be deactivated, and hide install/remove actions in Module Store.allowOutsideRead/outsideReadRootslet a module's API handler read paths outside its own directory (empty roots list = unrestricted read; used byfile-explorer).
Beyond the manifest, some modules are visibility-gated in code (src/lib/module-store/developer-mode-modules.ts): agent-orchestrator and terminal are only visible when Developer mode is enabled (terminal additionally refuses to run without it), and video-studio is admin-only.
Store categories
categories assigns the module to one or more store categories. Allowed keys (see src/lib/module-categories.ts):
productivity,communication,documents,legal,finance,marketing,media,automation,developer,insights,utilities
A module can appear in multiple categories at once. Unknown keys are dropped during manifest parsing and logged as a server warning ([module-store] Module "<slug>" declares unknown categories ...); local modules without categories fall back to utilities in the store UI. Category labels are localized (de/en/it) via moduleCategory.* translation keys.
Store icon image
If a module ships an icon.png (or icon.svg/icon.webp/icon.jpg/icon.jpeg) in its root directory, the store uses it as the app icon, served through /api/modules/<slug>/assets/<iconFile>. Without an icon file the store falls back to the matching navigation icon from public/icons/navigation/ or a generated gradient tile with the manifest icon glyph.
Runtime endpoints
- assets:
/api/modules/[slug]/assets/[...assetPath] - module API handler:
/api/modules/[slug]/api/[...endpointPath] - storage API:
/api/modules/[slug]/storage
Auth model for the module API handler:
- web users:
clapilot_sessioncookie - native runtime machine calls: Bearer token minted by
/api/auth/agent/system-token
HTML assets get window.__CLAPILOT_MODULE_CONTEXT__ injection with apiBase, storageUrl, and assetsBase.
Injected HTML remains no-store. Other module assets use a private five-minute cache, one-hour stale-while-revalidate window, and a weak ETag based on file modification time and size.
All three endpoints resolve only the effective module inventory. An uninstalled bundled slug returns 404 with {"error":"module_not_installed"}. The module page and module-owned settings pages are likewise unavailable, and sidebar/settings entries are filtered out. Native agent-tool gating follows in the companion agent-runtime change.
Context publishing
Modules report page context so chat/live agents know what the user is looking at:
- React-rendered modules pass a context object to the shell via their
onContextChangeprop. - Iframe modules
postMessagetheir context to the parent window; the module page relays it.
The native runtime maps the active module slug to tool families and action rules via services/clapilot-agent/src/page-capabilities.mjs (for example tax-manager activates the tax_manager_* tools).
Lifecycle: publish and install
- discover local modules via Module Store
- publish signed archive to hub (admin)
- install from hub with integrity checks
- for bundled install, run migrations once and persist the instance-wide install override without copying the bundled source
API surface: /api/module-store/* (local, catalog, publish, install, install-bundled, deactivate-bundled, delete, set-icon, set-menu-visibility).
Store experience
/modules renders an App Store-style storefront for modules, skills, and widgets:
- category chip filters plus full-text search across name, slug, description, and category labels
- category sections with two-column app rows (icon, name, description, action pill) and "see all" expansion
- install / update / uninstall actions: bundled cards show their DB-backed install state, hub modules install from the connected hub, and installed hub modules show an update pill when the hub has a newer version
- an app detail dialog with description, categories, version/source/entry metadata, and admin management (icon, menu visibility, publish, deactivate/delete with inline confirmation)
Implementation: src/components/module-store-content.tsx, src/components/skill-store-content.tsx, src/components/mini-apps-content.tsx, shared UI kit in src/components/store/store-app-kit.tsx.
Bundled modules shipped by Clapilot
agent-orchestrator(hidden unless Developer mode is enabled)agents(Spezial-Agenten overview)athlete-brand-matchingbook-appointment(Termine)call-agentcanvascasesexcel-canvasfile-explorernewsnotizensocial-mediatax-manager(Finanzen)terminal(hidden unless Developer mode is enabled)video-studio(admin-only)website-canvaswiki(fixed)word-canvas
accounting (Buchhaltung) is a special case: it renders natively in React (src/components/modules/accounting-module.tsx) without a bundled-modules/ directory.
Detailed docs:
