Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
70b16db
Align plugin settings with capability parameters
matteomekhail Aug 14, 2026
485a9f3
Add safe directory operations to scoped filesystems
matteomekhail Aug 14, 2026
e3535b4
Resolve custom plugin defaults consistently
matteomekhail Aug 14, 2026
dea9ce8
Divide required and optional plugin settings
matteomekhail Aug 14, 2026
d343ba9
fix: complete setting metadata integration
matteomekhail Aug 14, 2026
848eb8d
fix: harden sandboxed file deletion
matteomekhail Aug 14, 2026
2eff259
fix: preserve unset plugin settings
matteomekhail Aug 14, 2026
badf10c
fix: test setting groups and target deep links
matteomekhail Aug 14, 2026
5062c9d
Merge branch 'codex/issue-3-plugin-setting-parameters' into codex/iss…
matteomekhail Aug 14, 2026
d75c541
Merge branch 'codex/issue-11-custom-setting-defaults' into codex/issu…
matteomekhail Aug 14, 2026
9d7e9d5
fix: resolve generated settings safely at runtime
matteomekhail Aug 14, 2026
144d594
fix: preserve safe paths and isolate cache operations
matteomekhail Aug 14, 2026
0eb3aaf
Merge branch 'codex/issue-3-plugin-setting-parameters' into codex/iss…
matteomekhail Aug 14, 2026
153c666
fix: preserve unchanged settings cache identity
matteomekhail Aug 14, 2026
a9f0142
fix: handle unavailable sandbox roots safely
matteomekhail Aug 14, 2026
2f15ae1
fix: apply manifest defaults to capability gates
matteomekhail Aug 14, 2026
c6cb1eb
Merge branch 'codex/issue-11-custom-setting-defaults' into codex/issu…
matteomekhail Aug 14, 2026
e203a5c
fix: keep cache filesystem operations isolated
matteomekhail Aug 15, 2026
ce2f1a8
Merge branch 'codex/issue-10-plugin-filesystem' into codex/issue-11-c…
matteomekhail Aug 15, 2026
db3dfd2
Merge branch 'codex/issue-11-custom-setting-defaults' into codex/issu…
matteomekhail Aug 15, 2026
3927e37
Revert "Merge branch 'codex/issue-10-plugin-filesystem' into codex/is…
matteomekhail Aug 15, 2026
cc6c9d7
Revert "Merge branch 'codex/issue-11-custom-setting-defaults' into co…
matteomekhail Aug 15, 2026
e471b60
Merge remote-tracking branch 'origin/dev' into codex/issue-11-custom-…
matteomekhail Aug 15, 2026
21ff027
Merge branch 'codex/issue-11-custom-setting-defaults' into codex/issu…
matteomekhail Aug 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ import java.io.File
data class CompleteExampleSettings(
@PluginSetting(
description = "Public configuration value example",
defaultValue = "default_api_key"
defaultValue = "default_api_key",
minLength = 8,
semanticTypes = ["text/plain"]
) val apiKey: String? = "default_api_key",

@PluginSetting(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<resources>
<string name="plugin_settings_required">Obbligatorie</string>
<string name="plugin_settings_optional">Facoltative</string>
<string name="app_name">PluginToolkit</string>
<string name="section_runner">Runner</string>
<string name="nav_main">Dashboard</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,8 @@
<string name="plugin_settings_actions">Actions</string>
<string name="plugin_settings_custom">Custom Settings</string>
<string name="plugin_settings_global_defaults">Global Parameter Defaults</string>
<string name="plugin_settings_required">Required</string>
<string name="plugin_settings_optional">Optional</string>
<string name="plugin_settings_capability">Capability: %1$s</string>
<string name="settings_enum_unlock_message">Configure required settings to unlock options</string>
<string name="settings_locked_capability">Locked capability: %1$s</string>
Expand Down Expand Up @@ -474,4 +476,4 @@
<string name="repo_filter_label">Filter:</string>
<string name="repo_sort_label">Sort:</string>
<string name="repo_sync_all">Sync All</string>
</resources>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import androidx.navigation3.runtime.NavKey
import org.wip.plugintoolkit.features.navigation.GlobalRouter
import org.wip.plugintoolkit.features.navigation.model.Screen
import org.wip.plugintoolkit.features.plugin.logic.PluginManager
import org.wip.plugintoolkit.features.plugin.model.resolveProvidedValues
import org.wip.plugintoolkit.features.plugin.ui.lockedClickInterceptor
import org.wip.plugintoolkit.shared.components.ToolkitTextField
import plugintoolkit.composeapp.generated.resources.Res
Expand Down Expand Up @@ -254,8 +255,9 @@ private fun CapabilitiesPalette(
)
)
caps.forEach { cap ->
val isReady = remember(cap, settingsStore.settings, manifest?.settings) {
cap.isReady(settingsStore.settings, manifest?.settings)
val providedSettings = settingsStore.resolveProvidedValues(manifest)
val isReady = remember(cap, providedSettings, manifest?.settings) {
cap.isReady(providedSettings, manifest?.settings)
}

val targetSettingKey = cap.requiredLocks.firstOrNull()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import org.wip.plugintoolkit.core.utils.FileSystem
import org.wip.plugintoolkit.features.job.logic.JobManager
import org.wip.plugintoolkit.features.job.model.JobStatus
import org.wip.plugintoolkit.features.plugin.model.PluginSettingsStore
import org.wip.plugintoolkit.features.plugin.model.resolveCustomSettings
import org.wip.plugintoolkit.features.settings.logic.SettingsRepository
import org.wip.plugintoolkit.features.settings.model.PluginUnplugBehavior
import org.wip.plugintoolkit.features.plugin.utils.PluginCompatibilityUtils
Expand Down Expand Up @@ -287,6 +288,7 @@ class PluginLifecycleManager(
}

val decryptedStore = store.copy(settings = decryptedSettings)
.withResolvedAutogeneratedSettings(manifest?.settings.orEmpty())
_pluginSettingsState.update { it + (pkg to decryptedStore) }
return decryptedStore
}
Expand All @@ -296,7 +298,8 @@ class PluginLifecycleManager(
val settingsFile = "${plugin.installPath}/settings.json"

val manifest = getManifest(pkg)
val encryptedSettings = store.settings.mapValues { (key, value) ->
val resolvedStore = store.withResolvedAutogeneratedSettings(manifest?.settings.orEmpty())
val encryptedSettings = resolvedStore.settings.mapValues { (key, value) ->
val isSecret = manifest?.settings?.get(key)?.secret == true
if (isSecret && value is kotlinx.serialization.json.JsonPrimitive && value.isString) {
val encrypted = org.wip.plugintoolkit.core.utils.SecureStorage.encrypt(value.content)
Expand All @@ -305,12 +308,12 @@ class PluginLifecycleManager(
value
}
}
val storeToSave = store.copy(settings = encryptedSettings)
val storeToSave = resolvedStore.copy(settings = encryptedSettings)

try {
fileSystem.writeFile(settingsFile, json.encodeToString(storeToSave))
// Update cache with the decrypted store
_pluginSettingsState.update { it + (pkg to store) }
_pluginSettingsState.update { it + (pkg to resolvedStore) }
} catch (t: Throwable) {
Logger.e(t) { "Failed to save settings for $pkg" }
}
Expand All @@ -330,17 +333,10 @@ class PluginLifecycleManager(
val installPath = plugin?.installPath ?: ""
val jarFullPath = plugin?.let { "${it.installPath}/${it.jarFileName}" }

val storedSettings = overriddenSettings ?: loadPluginSettings(pkg)
val actualManifest = manifest ?: getManifest(pkg)
val mergedSettings = mutableMapOf<String, JsonElement>()

// 1. Manifest defaults
actualManifest?.settings?.forEach { (key, meta) ->
meta.defaultValue?.let { mergedSettings[key] = it }
}

// 2. User overrides
mergedSettings.putAll(storedSettings.settings)
val storedSettings = (overriddenSettings ?: loadPluginSettings(pkg))
.withResolvedAutogeneratedSettings(actualManifest?.settings.orEmpty())
val mergedSettings = storedSettings.resolveCustomSettings(actualManifest)

val pluginLogger = jobManager.getPluginLogger(pkg, jobId)
val progressReporter = object : ProgressReporter {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package org.wip.plugintoolkit.features.plugin.logic

import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonPrimitive
import org.wip.plugintoolkit.api.DataType
import org.wip.plugintoolkit.api.PrimitiveType
import org.wip.plugintoolkit.api.SettingMetadata
import org.wip.plugintoolkit.features.flows.logic.PathPatternResolver
import org.wip.plugintoolkit.features.plugin.model.PluginSettingsStore
import org.wip.plugintoolkit.features.plugin.utils.SettingsUtils

internal fun resolveAutogeneratedSettings(
metadata: Map<String, SettingMetadata>,
settings: Map<String, JsonElement>,
additionalValues: Map<String, JsonElement> = emptyMap()
): Map<String, JsonElement> {
val resolvedSettings = settings.toMutableMap()
val defaults = metadata.mapNotNull { (key, value) -> value.defaultValue?.let { key to it } }.toMap()

repeat(metadata.size.coerceAtLeast(1)) {
var changed = false
metadata.forEach { (key, settingMetadata) ->
val pattern = settingMetadata.autogeneratedPattern?.takeIf { it.isNotBlank() } ?: return@forEach
val availableValues = defaults + additionalValues + resolvedSettings
val stringValues = availableValues.mapValues { (valueKey, value) ->
val valueType = metadata[valueKey]?.type
if (valueType != null) SettingsUtils.jsonToString(value, valueType) else value.toString().trim('"')
}
val generated = runCatching { PathPatternResolver.tryResolve(pattern, stringValues) }.getOrNull()
?: return@forEach
val generatedValue = generatedSettingValue(generated, settingMetadata.type) ?: return@forEach
if (resolvedSettings[key] != generatedValue) {
resolvedSettings[key] = generatedValue
changed = true
}
}
if (!changed) return resolvedSettings
}

return resolvedSettings
}

internal fun PluginSettingsStore.withResolvedAutogeneratedSettings(
metadata: Map<String, SettingMetadata>
): PluginSettingsStore {
val resolved = resolveAutogeneratedSettings(metadata, settings, globalParams)
return if (resolved == settings) this else copy(settings = resolved)
}

private fun generatedSettingValue(value: String, type: DataType): JsonElement? = when (type) {
is DataType.Primitive -> when (type.primitiveType) {
PrimitiveType.STRING, PrimitiveType.ANY, PrimitiveType.UNKNOWN -> JsonPrimitive(value)
PrimitiveType.BOOLEAN -> value.toBooleanStrictOrNull()?.let(::JsonPrimitive)
PrimitiveType.INT -> value.toIntOrNull()?.let(::JsonPrimitive)
PrimitiveType.LONG -> value.toLongOrNull()?.let(::JsonPrimitive)
PrimitiveType.SHORT -> value.toShortOrNull()?.let { JsonPrimitive(it.toInt()) }
PrimitiveType.BYTE -> value.toByteOrNull()?.let { JsonPrimitive(it.toInt()) }
PrimitiveType.DOUBLE -> value.toDoubleOrNull()?.let(::JsonPrimitive)
PrimitiveType.FLOAT -> value.toFloatOrNull()?.let { JsonPrimitive(it.toDouble()) }
PrimitiveType.UNIT -> null
}
else -> value.takeIf { it.isNotBlank() }?.let { runCatching { SettingsUtils.stringToJson(it, type) }.getOrNull() }
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,23 @@ package org.wip.plugintoolkit.features.plugin.model

import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement
import org.wip.plugintoolkit.api.PluginManifest

@Serializable
data class PluginSettingsStore(
val settings: Map<String, JsonElement> = emptyMap(),
val globalParams: Map<String, JsonElement> = emptyMap(),
val capabilityParams: Map<String, Map<String, JsonElement>> = emptyMap()
)

fun PluginManifest.defaultCustomSettings(): Map<String, JsonElement> = settings.orEmpty().mapNotNull { (key, metadata) ->
metadata.defaultValue?.let { key to it }
}.toMap()

/** Manifest defaults with persisted user values taking precedence. */
fun PluginSettingsStore.resolveCustomSettings(manifest: PluginManifest?): Map<String, JsonElement> =
(manifest?.defaultCustomSettings() ?: emptyMap()) + settings

/** Values available to generated inputs and lock evaluation in the settings UI. */
fun PluginSettingsStore.resolveProvidedValues(manifest: PluginManifest?): Map<String, JsonElement> =
resolveCustomSettings(manifest) + globalParams
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import org.wip.plugintoolkit.api.Capability
import org.wip.plugintoolkit.api.PluginEntry
import org.wip.plugintoolkit.core.model.localized
import org.wip.plugintoolkit.core.theme.ToolkitTheme
import org.wip.plugintoolkit.features.plugin.model.resolveProvidedValues
import org.wip.plugintoolkit.features.navigation.GlobalRouter
import org.wip.plugintoolkit.features.navigation.LocalGlobalRouter
import org.wip.plugintoolkit.features.navigation.model.Screen
Expand Down Expand Up @@ -172,7 +173,7 @@ fun DirectExecutionSidebar(
val manifest = plugin.getManifest().getOrThrow()
val pluginManager: org.wip.plugintoolkit.features.plugin.logic.PluginManager = org.koin.compose.koinInject()
val settingsStore = pluginManager.loadPluginSettings(pluginId)
val settings = settingsStore.settings + settingsStore.globalParams
val settings = settingsStore.resolveProvidedValues(manifest)
val pluginLocksState by pluginManager.pluginLocksState.collectAsState()
val locks = pluginLocksState[pluginId] ?: pluginLocksState.values.fold(emptyMap<String, Boolean>()) { acc, map -> acc + map }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,13 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import kotlinx.serialization.json.JsonPrimitive
import org.wip.plugintoolkit.api.DataType
import org.wip.plugintoolkit.api.PrimitiveType
import org.jetbrains.compose.resources.stringResource
import org.wip.plugintoolkit.api.Capability
import org.wip.plugintoolkit.api.ParameterRole
import org.wip.plugintoolkit.api.PluginManifest
import org.wip.plugintoolkit.features.job.model.BackgroundJob
import org.wip.plugintoolkit.features.job.model.JobStatus
import org.wip.plugintoolkit.features.plugin.model.resolveProvidedValues
import org.wip.plugintoolkit.features.navigation.model.Screen
import org.wip.plugintoolkit.features.plugin.viewmodel.PluginViewModel
import org.wip.plugintoolkit.shared.components.plugin.JobResultCard
Expand Down Expand Up @@ -122,12 +120,8 @@ fun PluginContent(
val providedSettings = remember(pluginId, pluginSettingsState) {
val store = if (pluginId != null) pluginSettingsState[pluginId] ?: pluginManager.loadPluginSettings(pluginId) else null
val manifest = viewModel.selectedPlugin?.getManifest()?.getOrNull()
val manifestDefaults = (manifest?.settings?.mapValues { (_, meta) ->
meta.defaultValue ?: if (meta.type is DataType.Primitive && (meta.type as DataType.Primitive).primitiveType == PrimitiveType.BOOLEAN) {
JsonPrimitive(false)
} else null
}?.filterValues { it != null } ?: emptyMap()) as Map<String, kotlinx.serialization.json.JsonElement>
manifestDefaults + (store?.settings ?: emptyMap()) + (store?.globalParams ?: emptyMap())
(store ?: org.wip.plugintoolkit.features.plugin.model.PluginSettingsStore())
.resolveProvidedValues(manifest)
}

if (selectedCapability == null) {
Expand Down
Loading