Commit 68981ee
feat(plugins): wire asset unregistration, fix restart persistence, add bundle convention docs (#256)
* feat: plugin-driven asset registration system
Replace hardcoded slint asset type with a generic plugin asset registration
mechanism. Plugins declare asset types in their plugin.yml manifests, and
the server provides generic CRUD endpoints parameterized by type_id.
Backend:
- Extend PluginManifest with optional assets section (PluginAssetSpec)
- Add PluginAssetRegistry with thread-safe registration/lookup
- Generic handlers: list, upload, delete, serve, update for plugin assets
- Discovery endpoint GET /api/v1/asset-types returns core + plugin types
- Add PluginAsset, AssetContentKind, AssetTypeInfo to streamkit-api
- Register slint plugin assets via plugins/native/slint/plugin.yml
- Move slint files to samples/slint/system/ and update pipeline refs
- Update permissions to include plugin asset paths
Frontend:
- Add useAssetTypes() hook for dynamic type discovery
- Add generic usePluginAssets(typeId) CRUD hooks
- Add React Query hooks to imageAssets.ts and fontAssets.ts
- New AssetCard component with discriminated UnifiedAsset union
- New data-driven AssetLibrary replacing AudioAssetLibrary
- Update DesignView drag-drop for plugin asset node creation
- Update ControlPane to use unified AssetLibrary
Signed-off-by: Devin AI <devin@cognition.ai>
Signed-off-by: StreamKit Devin <devin@streamkit.dev>
Co-Authored-By: Claudio Costa <cstcld91@gmail.com>
* fix: remove unused files and exports flagged by knip
- Delete AudioAssetCard.tsx and AudioAssetLibrary.tsx (replaced by AssetCard/AssetLibrary)
- Remove unused getAssetId export and AssetType type export from AssetCard
- Remove unused fetchPluginAssetContent and updatePluginAssetContent (Phase 2)
Signed-off-by: Devin AI <devin@cognition.ai>
Signed-off-by: StreamKit Devin <devin@streamkit.dev>
Co-Authored-By: Claudio Costa <cstcld91@gmail.com>
* style: format AssetCard.tsx with prettier
Signed-off-by: Devin AI <devin@cognition.ai>
Signed-off-by: StreamKit Devin <devin@streamkit.dev>
Co-Authored-By: Claudio Costa <cstcld91@gmail.com>
* fix: remove unused AssetType and getId definitions
Flagged by ESLint no-unused-vars after removing their exports.
Signed-off-by: Devin AI <devin@cognition.ai>
Signed-off-by: StreamKit Devin <devin@streamkit.dev>
Co-Authored-By: Claudio Costa <cstcld91@gmail.com>
* fix: address review findings — bundle optional, drag guard, path traversal
- Make PluginManifest.bundle Optional so local plugin.yml files
(which lack a bundle section) can be deserialized correctly.
- Prevent drag-drop for image/font assets that don't have drop handlers
yet, avoiding broken node creation on the canvas.
- Add path traversal guard (../ check) to delete_handler for
defense-in-depth consistency with serve_handler and update_handler.
Signed-off-by: Devin AI <devin@cognition.ai>
Signed-off-by: StreamKit Devin <devin@streamkit.dev>
Co-Authored-By: Claudio Costa <cstcld91@gmail.com>
* test: add manifest deserialization tests for bundle optionality
Regression tests for the bug where local plugin manifests (without a
bundle section) failed to deserialize. Tests both with and without
the bundle field to prevent recurrence.
Signed-off-by: Devin AI <devin@cognition.ai>
Signed-off-by: StreamKit Devin <devin@streamkit.dev>
Co-Authored-By: Claudio Costa <cstcld91@gmail.com>
* fix: enforce max_size_bytes limit in update_handler
The PUT endpoint for in-place text file updates was missing the
per-asset-type size check, allowing writes up to the 100 MiB outer
body limit. Now rejects bodies exceeding max_size_bytes before writing.
Signed-off-by: Devin AI <devin@cognition.ai>
Signed-off-by: StreamKit Devin <devin@streamkit.dev>
Co-Authored-By: Claudio Costa <cstcld91@gmail.com>
* fix: address review findings — security, correctness, and cleanup
Security:
- Add validate_file_in_directory to serve_handler (symlink bypass).
- Validate type_id is [a-zA-Z0-9_-]+ and system_dir has no '..' at
registration time to prevent path injection from plugin manifests.
- Reject '.' as a filename in validate_filename.
- Enforce max_size_bytes in update_handler (PUT bypass of per-type limit).
Correctness:
- Fix plugin delete from 'All' view: call deletePluginAsset directly
with the asset's own type_id instead of pre-parameterized hook that
captures empty string when no plugin type is selected.
- Upload handler returns 201 Created instead of 200.
- copy-plugins-native now copies plugin.yml alongside .so files so
read_local_plugin_manifest can find manifests in the flat layout.
- read_local_plugin_manifest searches for {stem}.plugin.yml in addition
to plugin.yml in the same directory.
- Add viewer role access to system plugin assets (samples/*/system/*).
Cleanup:
- Remove unused AssetContentKind from API types (naming inconsistency
with backend AssetContentType; editable bool already covers the need).
- Replace 'as never' cast with type-safe appendIfMatches helper.
- Expand permissions.rs glob comment explaining intentional breadth.
- Skip core asset queries when filtered to a different type (enabled flag).
- Remove unused useDeletePluginAsset hook.
- Add TODO comment for multi-plugin 'All' view limitation.
Signed-off-by: Devin AI <devin@cognition.ai>
Signed-off-by: StreamKit Devin <devin@streamkit.dev>
Co-Authored-By: Claudio Costa <cstcld91@gmail.com>
* fix: address review findings — security, correctness, and cleanup
Security:
- Add validate_file_in_directory to serve_handler (symlink bypass).
- Validate type_id is [a-zA-Z0-9_-]+ and system_dir has no '..' at
registration time to prevent path injection from plugin manifests.
- Reject '.' as a filename in validate_filename.
- Enforce max_size_bytes in update_handler (PUT bypass of per-type limit).
Correctness:
- Fix plugin delete from 'All' view: call deletePluginAsset directly
with the asset's own type_id instead of pre-parameterized hook that
captures empty string when no plugin type is selected.
- Upload handler returns 201 Created instead of 200.
- copy-plugins-native now copies plugin.yml alongside .so files so
read_local_plugin_manifest can find manifests in the flat layout.
- read_local_plugin_manifest searches for {stem}.plugin.yml in addition
to plugin.yml in the same directory.
- Add viewer role access to system plugin assets (samples/*/system/*).
Cleanup:
- Remove unused AssetContentKind from API types (naming inconsistency
with backend AssetContentType; editable bool already covers the need).
- Replace 'as never' cast with type-safe appendIfMatches helper.
- Expand permissions.rs glob comment explaining intentional breadth.
- Skip core asset queries when filtered to a different type (enabled flag).
- Remove unused useDeletePluginAsset hook.
- Add TODO comment for multi-plugin 'All' view limitation.
Signed-off-by: Devin AI <devin@cognition.ai>
Signed-off-by: StreamKit Devin <devin@streamkit.dev>
Co-Authored-By: Claudio Costa <cstcld91@gmail.com>
* style: format TypeScript files
Signed-off-by: Devin AI <devin@cognition.ai>
Signed-off-by: StreamKit Devin <devin@streamkit.dev>
Co-Authored-By: Claudio Costa <cstcld91@gmail.com>
* fix: reject empty type_id and update TS bundle type to allow null
- Add is_empty() check before Iterator::all to prevent vacuous truth
accepting empty type_id at registration time.
- Update TypeScript PluginManifest.bundle to PluginBundle | null to
match the Rust Option<PluginBundle> change.
- Add optional chaining to bundle.size_bytes access in MarketplacePanels.
Signed-off-by: Devin AI <devin@cognition.ai>
Signed-off-by: StreamKit Devin <devin@streamkit.dev>
Co-Authored-By: Claudio Costa <cstcld91@gmail.com>
* fix: harden plugin asset security, wire marketplace registration, multi-plugin All view
Security:
- Reject absolute paths and path-traversal in system_dir using
Path::components() instead of substring matching (C1, S3).
- Add validate_file_in_directory to upload_handler for defense-in-depth (C2).
- Post-sanitization guard in sanitize_filename rejects '.' and '..' (S2).
- Change scan_directory param from &PathBuf to &Path (clippy lint).
Correctness:
- Wire PluginAssetRegistry into marketplace installer so plugins installed
at runtime register their asset types immediately (not only at startup).
- Use useQueries to fetch all plugin types in the 'All' view, not just the
first one (S4).
- Remove unused usePluginAssets hook after useQueries migration.
- Downgrade pluginAssets.ts fetch logging from info to debug.
Testing:
- Add 29 unit tests covering sanitize_filename, validate_filename,
validate_file_in_directory, and register validation (empty type_id,
traversal type_id, absolute system_dir, dotdot system_dir, dotdot
substring in dirname, valid spec).
Signed-off-by: Devin AI <devin@cognition.ai>
Signed-off-by: StreamKit Devin <devin@streamkit.dev>
Co-Authored-By: Claudio Costa <cstcld91@gmail.com>
* fix: TOCTOU in delete/serve/update, upload validation, node_kind, memoization
Security:
- Eliminate TOCTOU race in delete/serve/update handlers: drop exists()
pre-check, map io::ErrorKind::NotFound inside validate_file_in_directory.
- Fix upload_handler: validate parent directory (canonicalize user_dir)
instead of non-existent file_path (canonicalize fails on missing files).
- serve_handler now validates file extension against registered asset type.
- sanitize_filename('') now returns '_invalid_' instead of empty string.
- Document icon_hint Option<String> vs String asymmetry.
Correctness:
- Marketplace installer uses expected_kind (from manifest) instead of
hardcoded 'plugin::native::' prefix for asset registration node_kind.
Performance:
- Stabilize pluginQueries dependency in AssetLibrary via useRef shallow
comparison — prevents cascading useMemo recomputation every render.
- useUploadPluginAsset short-circuits with rejection when typeId is empty.
Logging:
- assetTypes.ts: logger.info → logger.debug on fetch calls.
Signed-off-by: Devin AI <devin@cognition.ai>
Signed-off-by: StreamKit Devin <devin@streamkit.dev>
Co-Authored-By: Claudio Costa <cstcld91@gmail.com>
* fix: clippy CI — allow unwrap_used in tests, fix redundant closure
Signed-off-by: Devin AI <devin@cognition.ai>
Signed-off-by: StreamKit Devin <devin@streamkit.dev>
Co-Authored-By: Claudio Costa <cstcld91@gmail.com>
* fix: lower update_handler DefaultBodyLimit from 100 MiB to 10 MiB
The PUT route for text asset updates buffers body: String in memory
before checking per-type max_size_bytes (typically 1 MiB). The 100 MiB
outer limit allowed authenticated users to force large allocations that
would be immediately rejected. Lower to 10 MiB to bound memory while
still being generous for text content editing.
Signed-off-by: Devin AI <devin@cognition.ai>
Signed-off-by: StreamKit Devin <devin@streamkit.dev>
Co-Authored-By: Claudio Costa <cstcld91@gmail.com>
* feat(plugins): wire unregister_plugin into plugin unload/delete path
Call plugin_asset_registry.unregister_plugin() when a plugin is deleted
via the HTTP API (delete_plugin_handler) and when an existing plugin is
unloaded before marketplace re-installation (load_plugin).
This ensures CRUD endpoints stop serving stale asset types after a plugin
is removed. User-uploaded files are intentionally left in place for
potential re-install.
Remove the #[allow(dead_code)] annotation from unregister_plugin now that
it is wired in.
Add unit tests for register-then-unregister flows:
- unregister_removes_all_types_for_plugin
- unregister_leaves_other_plugins_intact
Add tests for write_manifest_yml:
- write_manifest_yml_creates_yaml_next_to_entrypoint
- write_manifest_yml_nested_entrypoint
Closes #255
Signed-off-by: StreamKit Devin <devin@streamkit.dev>
Co-Authored-By: Claudio Costa <cstcld91@gmail.com>
* docs(plugins): add plugin bundle convention design doc
Add architecture/plugin-bundle-convention.md proposing a unified directory
layout for plugins (local and marketplace). The doc covers:
- Current state (bare .so vs extracted bundle directories)
- Proposed convention (plugin.yml + entrypoint + optional extras)
- Incremental migration path (backward-compatible, no breaking changes)
- Already-done steps and future work items
Update publishing-plugins.md to reference the new convention and mention
that plugin.yml is written automatically during marketplace installation.
Add the new doc to the sidebar in astro.config.mjs.
Ref #254
Signed-off-by: StreamKit Devin <devin@streamkit.dev>
Co-Authored-By: Claudio Costa <cstcld91@gmail.com>
* fix(plugins): use active record plugin_id for asset unregistration
For marketplace plugins, use record.plugin_id (which matches manifest.id
used during registration) instead of summary.original_kind. This avoids
a silent mismatch if a plugin author sets a manifest id that differs from
the binary's reported kind.
For non-marketplace plugins, fall back to original_kind which by
convention equals the manifest id.
Signed-off-by: StreamKit Devin <devin@streamkit.dev>
Co-Authored-By: Claudio Costa <cstcld91@gmail.com>
* style: format unregister_id binding
Signed-off-by: StreamKit Devin <devin@streamkit.dev>
Co-Authored-By: Claudio Costa <cstcld91@gmail.com>
* feat(plugins): add install-plugin recipe for single-plugin build+install
Adds `just install-plugin <name>` which builds a single native plugin
and copies the .so + plugin.yml to .plugins/native/. Idempotent — safe
to re-run after code changes.
Usage: just install-plugin slint
just install-plugin pocket-tts
Signed-off-by: StreamKit Devin <devin@streamkit.dev>
Co-Authored-By: Claudio Costa <cstcld91@gmail.com>
* chore(plugins): bump all native plugin versions to 0.2.0
Minor version bump for all 11 official native plugins (Cargo.toml and
plugin.yml) to trigger marketplace rebuilds.
Regenerate marketplace/official-plugins.json with updated versions.
Signed-off-by: StreamKit Devin <devin@streamkit.dev>
Co-Authored-By: Claudio Costa <cstcld91@gmail.com>
* refactor(api): replace stringly-typed AssetTypeInfo.source with enum
Introduce AssetTypeSource enum with Core and Plugin variants instead of
magic "core"/"plugin" string values. Updated all server usages and
regenerated TypeScript types.
Also normalise core type_id naming to singular form (images→image,
fonts→font) for consistency with plugin type_ids.
Adds unit tests for type_id collision detection, max_size_bytes cap,
and registered_type_ids sync cache.
Signed-off-by: StreamKit Devin <devin@streamkit.dev>
Co-Authored-By: Claudio Costa <cstcld91@gmail.com>
* refactor(api): replace stringly-typed AssetTypeInfo.source with enum
Introduce AssetTypeSource enum with Core and Plugin variants instead of
magic "core"/"plugin" string values. Updated all server usages and
regenerated TypeScript types.
Also normalise core type_id naming to singular form (images→image,
fonts→font) for consistency with plugin type_ids.
Adds unit tests for type_id collision detection, max_size_bytes cap,
and registered_type_ids sync cache.
Signed-off-by: StreamKit Devin <devin@streamkit.dev>
Co-Authored-By: Claudio Costa <cstcld91@gmail.com>
* fix(permissions): replace broad plugin asset wildcards with dynamic patterns
Remove the overly broad `samples/*/system/*` and `samples/*/user/*`
wildcards from default role permissions. Instead, dynamically augment
permissions at request time with patterns scoped to actually-registered
plugin type IDs via PluginAssetRegistry::registered_type_ids().
This prevents theoretical access to unregistered type directories while
maintaining full access to registered plugin asset types.
Adds unit test verifying default roles contain no broad wildcards.
Signed-off-by: StreamKit Devin <devin@streamkit.dev>
Co-Authored-By: Claudio Costa <cstcld91@gmail.com>
* fix(marketplace): guard against empty entrypoint in write_manifest_yml
Add anyhow::ensure! check that manifest.entrypoint is non-empty before
writing plugin.yml. An empty entrypoint would cause .parent() to
traverse above the bundle directory.
Signed-off-by: StreamKit Devin <devin@streamkit.dev>
Co-Authored-By: Claudio Costa <cstcld91@gmail.com>
* feat(plugins): use bundle directory layout for install-plugin recipe
install-plugin now creates .plugins/native/<name>/ directories with the
compiled library and plugin.yml manifest inside, matching the marketplace
bundle convention.
Also updates the plugin loader to scan one level of subdirectories for
bundle-layout plugins while maintaining backward compatibility with flat
.so files at the top level.
Signed-off-by: StreamKit Devin <devin@streamkit.dev>
Co-Authored-By: Claudio Costa <cstcld91@gmail.com>
* fix(ui): improve AssetLibrary robustness and dynamic type filters
- Aggregate plugin query loading/error states into UI indicators
- Add typeFilter check for plugin entries in buildUnifiedItems
- Normalise type filter values to match singular type_ids (image, font)
- Generate type filter buttons dynamically from asset-types API response
- Make format dropdown show counts per format from actual asset data
- Extract aggregateQueryState helper to reduce hook complexity
Signed-off-by: StreamKit Devin <devin@streamkit.dev>
Co-Authored-By: Claudio Costa <cstcld91@gmail.com>
* fix(permissions): derive plugin asset patterns from actual paths
Address three Devin Review findings:
- Permission patterns now use the actual system_dir/user_dir from the
registry instead of hardcoded samples/{type_id}/ convention. This
ensures correct behaviour when a plugin uses a custom directory layout.
- Extract shared helpers (resolve_role_name, augment_plugin_asset_permissions)
so both get_permissions and get_role_and_permissions apply the same
plugin asset augmentation consistently.
- copy-plugins-native recipe now also copies pocket-tts plugin.yml for
consistency with the main plugin loop.
Adds unit test verifying custom system_dir produces correct permission
patterns.
Signed-off-by: StreamKit Devin <devin@streamkit.dev>
Co-Authored-By: Claudio Costa <cstcld91@gmail.com>
* fix(permissions): derive plugin asset patterns from actual paths
Address three Devin Review findings:
- Permission patterns now use the actual system_dir/user_dir from the
registry instead of hardcoded samples/{type_id}/ convention. This
ensures correct behaviour when a plugin uses a custom directory layout.
- Extract shared helpers (resolve_role_name, augment_plugin_asset_permissions)
so both get_permissions and get_role_and_permissions apply the same
plugin asset augmentation consistently.
- copy-plugins-native recipe now also copies pocket-tts plugin.yml for
consistency with the main plugin loop.
Adds unit test verifying custom system_dir produces correct permission
patterns.
Signed-off-by: StreamKit Devin <devin@streamkit.dev>
Co-Authored-By: Claudio Costa <cstcld91@gmail.com>
* fix(ui): reset format filter on type switch, fix drag affordance on non-draggable assets
- Reset formatFilter to 'all' when switching asset type tabs so stale
format selections from a previous type don't hide all results.
- Make AssetCard drag cursor conditional: only show grab cursor and set
draggable when the item actually supports drag-and-drop (audio and
plugin assets). Image/font cards no longer misleadingly appear
draggable.
- Thread isDraggable predicate through AssetLibrary → AssetCard.
- Fix user_dir derivation edge case: when a plugin sets a
single-component system_dir (e.g. 'system'), parent() returns an
empty path; now falls back to the default samples/{type_id}/user
instead of creating a bare 'user' directory at the project root.
- Add unit test for the single-component system_dir edge case.
Signed-off-by: StreamKit Devin <devin@streamkit.dev>
Co-Authored-By: Claudio Costa <cstcld91@gmail.com>
* fix(ui): reset format filter on type switch, fix drag affordance on non-draggable assets
- Reset formatFilter to 'all' when switching asset type tabs so stale
format selections from a previous type don't hide all results.
- Make AssetCard drag cursor conditional: only show grab cursor and set
draggable when the item actually supports drag-and-drop (audio and
plugin assets). Image/font cards no longer misleadingly appear
draggable.
- Thread isDraggable predicate through AssetLibrary → AssetCard.
- Fix user_dir derivation edge case: when a plugin sets a
single-component system_dir (e.g. 'system'), parent() returns an
empty path; now falls back to the default samples/{type_id}/user
instead of creating a bare 'user' directory at the project root.
- Add unit test for the single-component system_dir edge case.
Signed-off-by: StreamKit Devin <devin@streamkit.dev>
Co-Authored-By: Claudio Costa <cstcld91@gmail.com>
---------
Signed-off-by: Devin AI <devin@cognition.ai>
Signed-off-by: StreamKit Devin <devin@streamkit.dev>
Co-authored-by: StreamKit Devin <devin@streamkit.dev>
Co-authored-by: Claudio Costa <cstcld91@gmail.com>1 parent 5020a9c commit 68981ee
File tree
58 files changed
+3608
-666
lines changed- apps/skit/src
- server
- crates/api/src
- bin
- docs
- src/content/docs
- architecture
- guides
- marketplace
- plugins/native
- helsinki
- kokoro
- matcha
- nllb
- piper
- pocket-tts
- sensevoice
- slint
- supertonic
- vad
- whisper
- samples
- pipelines
- dynamic
- oneshot
- slint/system
- ui/src
- components
- panes
- services
- types
- generated
- views
- plugins
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
58 files changed
+3608
-666
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
| 19 | + | |
19 | 20 | | |
20 | 21 | | |
21 | 22 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
44 | 44 | | |
45 | 45 | | |
46 | 46 | | |
| 47 | + | |
47 | 48 | | |
48 | 49 | | |
49 | 50 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
225 | 225 | | |
226 | 226 | | |
227 | 227 | | |
228 | | - | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
229 | 232 | | |
230 | 233 | | |
231 | 234 | | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
232 | 286 | | |
233 | 287 | | |
234 | 288 | | |
| |||
855 | 909 | | |
856 | 910 | | |
857 | 911 | | |
| 912 | + | |
| 913 | + | |
| 914 | + | |
| 915 | + | |
| 916 | + | |
| 917 | + | |
| 918 | + | |
| 919 | + | |
| 920 | + | |
| 921 | + | |
| 922 | + | |
| 923 | + | |
| 924 | + | |
| 925 | + | |
| 926 | + | |
| 927 | + | |
| 928 | + | |
| 929 | + | |
| 930 | + | |
| 931 | + | |
| 932 | + | |
| 933 | + | |
| 934 | + | |
| 935 | + | |
| 936 | + | |
| 937 | + | |
| 938 | + | |
| 939 | + | |
| 940 | + | |
| 941 | + | |
| 942 | + | |
| 943 | + | |
| 944 | + | |
| 945 | + | |
| 946 | + | |
| 947 | + | |
| 948 | + | |
| 949 | + | |
| 950 | + | |
| 951 | + | |
| 952 | + | |
| 953 | + | |
| 954 | + | |
| 955 | + | |
| 956 | + | |
| 957 | + | |
| 958 | + | |
| 959 | + | |
| 960 | + | |
| 961 | + | |
| 962 | + | |
| 963 | + | |
| 964 | + | |
| 965 | + | |
858 | 966 | | |
0 commit comments