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-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
- installed bundled cards show a single
Installiertpill instead of a separate uninstall button; for admins, one click arms it into a redDeinstallierenpill (auto-reverting after five seconds), and a second click uninstalls the module without opening the detail dialog - an app detail dialog with description, categories, version/source/entry metadata, and admin management (menu visibility, publish, deactivate/delete with inline confirmation)
Module icons are supplied by the module and cannot be edited in the store. Store artwork falls back from an unavailable thumbnail to the original image before using the manifest glyph. The retired POST /api/module-store/set-icon endpoint returns 410 Gone for authenticated admins without changing files.
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-explorergrocery(Einkauf, not installed by default)3d-studio(3D-Studio, not installed by default)home-assistant(not installed by default)newsnotizensocial-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:
- Bundled Modules
- Widgets
- Agent Tool Contracts
- Website Canvas
- Canvas
- Excel Editor
- Word Editor
- Agent Orchestrator
- File Explorer
- Notizen
- Wiki
- Call & Fax Agent
- Social Media
- News
- Terminal
- Video Studio
- Buchhaltung
- Finanzen / Steuer-Manager
- Athlete-Brand Matching
- Home Assistant
- Einkauf (Grocery)
- 3D Studio
Module storage code boundary
Storage write, mkdir and delete actions accept only paths beneath the module data/ directory and reject symlink components. Handler, manifest and entrypoint files require the trusted module authoring/install path. File Explorer treats workspace, managed and bundled module trees as read-only for ordinary callers, including nested roots, aliases and parent deletion; administrators with Developer mode retain privileged authoring access. Existing starter data/notes.txt storage remains supported.
Installed module symlink targets inherit code protection even when their canonical location is in an otherwise writable workspace. Storage policy failures expose stable dataOnly/symlink/codeTarget codes with DE/EN/IT messages. Generated starter storage examples use data/ paths.
Module storage also preserves canonical code targets linked into data/, including deletion of their ancestors, and declared module entry files. Unresolvable module symlink loops remain inaccessible without disabling unrelated ordinary File Explorer writes.
The code boundary also rejects mutation aliases sharing an executable file inode through a hard link. Hard links between ordinary module data files remain usable. Dangling module link chains protect their eventual targets without disabling unrelated workspace writes.
Canonical module-code protection uses a fresh filesystem inventory across workspace, managed, and bundled collections with the same manifest identity validation as module discovery. Code links and shared inodes from any validated module protect their targets from storage mutations in every other module. Ordinary data/ trees are excluded from the code scan; declared entries/workers and links from code into data remain protected. Invalid collection entries and inaccessible subtrees do not disable unrelated File Explorer mutations. Collection directories and inaccessible code subtrees retain their read-only boundary.
A valid manifest establishes module-code protection before its entry exists, preventing activation through dangling links. Manifest paths and shared inodes retain activation protection even while invalid. Storage also treats every canonical module root as code outside that module's own data/, including modules installed inside another module's data directory; both modules' ordinary data remains writable. The shared protection helper is packaged inside File Explorer so relocating a complete module preserves its runtime imports.
Dangling module-directory aliases reserve their eventual manifest path before the directory exists, so a multi-file upload or prepared-directory rename cannot activate code through an otherwise writable canonical target.
Code-link protection includes every intermediate symlink path and reachable cycle member as well as its final target, preventing deletion or renaming of link ancestors. Write-protection inventories do not add module collections to ordinary readable roots. Malformed manifest coercions and non-file manifests are isolated consistently with module discovery and do not disable unrelated mutations; their manifest activation boundaries remain protected.
