Salamatrix Platform Foundation
Purpose
Salamatrix is the working technical name for the unified extensibility platform in Open Salamander. It is intended to connect the Salamander core, native plugins, scripted extensions, future language runtime adapters, shared UI services, and future SDK surface area without creating separate APIs for each runtime.
This document captures the initial platform foundation for the first MVP. It is a design and integration guide, not a commitment that all subsystems must be built at once.
Component name and location
The first Automation Framework provider component uses the technical name Salamatrix. The preferred source-tree location for the framework plugin is:
src/plugins/salamatrix/
The component should be packaged and registered as an Automation Framework/service plugin, not as a language runtime or user-facing command plugin. It may expose a small About/configuration page and demo commands while the MVP is developed, but its primary purpose is to provide shared services to other plugins and future script runtime adapters.
For the current native dialog/control surface and construction examples, see Salamatrix.UI framework and custom dialog guide.
Recommended user-visible names:
Salamatrix FrameworkSalamatrix UI FrameworkSalamatrix SDKfor developer-facing documentation and headers
Naming model
Use one umbrella platform name and technical subsystem names beneath it:
Salamatrix
├── Salamatrix.Core
├── Salamatrix.UI
├── Salamatrix.Commands
├── Salamatrix.FileOperations
├── Salamatrix.Runtime
├── Salamatrix.Events
├── Salamatrix.Extensions
├── Salamatrix.Storage
└── Salamatrix.SDK
For the MVP, only these names should be treated as active design targets:
Salamatrix.UISalamatrix.CommandsSalamatrix.FileOperationsSalamatrix.RuntimeSalamatrix.SidesSalamatrix.EventsSalamatrix.ExtensionsSalamatrix.Storage
Native naming
Native C++ headers can use Salamatrix as the namespace or API prefix:
Salamatrix::UI::CreateProgressDialog(...);
Salamatrix::Commands::Execute(...);
Salamatrix::FileOperations::CopyInteractive(...);
The exact ABI-safe interface exposed to plugins should still use abstract interfaces and versioned service identifiers instead of requiring C++ callers to link directly to a C++ implementation class.
Script naming
Scripts should normally see the existing product concept as their root object:
Salamander.UI.progress(...)
Salamander.Commands.execute(...)
Salamander.FileOperations.copy_interactive(...)
Salamander.Sides.Source.ActiveTab.Path
Salamander.SourceSide.Context().SelectedItems
Salamander.SourceSide.Context().FocusedItem
Salamander.Storage.get("lastPath")
Salamander.Clipboard.copy_text("generated output")
In this model, Salamatrix is the platform and SDK name, while Salamander is the user-friendly script root object. Future runtime adapters such as Python, JavaScript, PowerShell, or WSH may also expose advanced adapter metadata under Salamander.Runtime when needed.
Service identifiers and versioning
Each shared surface should be published as a versioned service. The service name is a stable ASCII identifier; the version is a monotonically increasing integer or a packed semantic version.
Initial service identifiers:
Salamatrix.UI
Salamatrix.Commands
Salamatrix.FileOperations
Salamatrix.Runtime
Salamatrix.Automation
Salamatrix.Sides
Salamatrix.Events
Salamatrix.Extensions
Salamatrix.Storage
Implemented C-style constants in the Salamatrix headers:
#define SALAMATRIX_SERVICE_UI "Salamatrix.UI"
#define SALAMATRIX_SERVICE_COMMANDS "Salamatrix.Commands"
#define SALAMATRIX_SERVICE_FILEOPERATIONS "Salamatrix.FileOperations"
#define SALAMATRIX_SERVICE_RUNTIME "Salamatrix.Runtime"
#define SALAMATRIX_SERVICE_AUTOMATION_ADAPTER "Salamatrix.Automation"
#define SALAMATRIX_SERVICE_SIDES "Salamatrix.Sides"
#define SALAMATRIX_SERVICE_EVENTS "Salamatrix.Events"
#define SALAMATRIX_SERVICE_EXTENSIONS "Salamatrix.Extensions"
#define SALAMATRIX_SERVICE_STORAGE "Salamatrix.Storage"
#define SALAMATRIX_UI_VERSION_1_0 0x00010000
#define SALAMATRIX_COMMANDS_VERSION_1_0 0x00010000
#define SALAMATRIX_FILEOPERATIONS_VERSION_1_0 0x00010000
#define SALAMATRIX_AUTOMATION_VERSION_1_0 0x00010000
#define SALAMATRIX_RUNTIME_VERSION_1_0 0x00010000
#define SALAMATRIX_SIDES_VERSION_1_0 0x00010000
#define SALAMATRIX_EVENTS_VERSION_1_0 0x00010000
#define SALAMATRIX_EXTENSIONS_VERSION_1_0 0x00010000
#define SALAMATRIX_STORAGE_VERSION_1_0 0x00010000
SALAMATRIX.SPL itself is an Automation Framework provider. The current host registration publishes the UI, Commands, FileOperations, Runtime broker, and Automation adapter, Sides, Events, Extensions, AI, and Storage services. The event service is fed by the host's plugin event callback and is also available to native runtime adapters.
Salamatrix.Runtime broker and execution contract
The first runtime broker contract is declared in:
src/plugins/salamatrix/salamatrix_runtime_api.h
IRuntimeService owns the process-local catalog of language runtime adapters. Adapters publish a stable runtime id, display/language metadata, supported entry point extensions, a runtime version, and flags describing in-process, out-of-process, bundled, compatibility, and persistent-extension capabilities. The broker supports registration, unregistration, enumeration, lookup by runtime id/version, and fallback lookup by entry point. IRuntimeAdapter also receives a versioned RuntimeExecutionRequest and returns a structured RuntimeExecutionResult with succeeded, failed, or cancelled status, HRESULT, process/exit information, a bounded UTF-16 output capture, and an adapter-owned error message. Requests carry an explicit working directory and a default two-minute timeout (clamped to one hour).
The Automation consumer bridge is the first adapter provider. It advertises the legacy Active Scripting engines that are actually available:
Automation.JScriptfor.js;Automation.VBScriptfor.vbs;Automation.ActivePythonfor.pys, when the legacy ActivePython COM engineAutomation.PHPScriptfor.phps, when the legacy PHPScript COM engine is
is installed;
installed.
These adapters are explicitly marked as in-process compatibility adapters. Independent runtime provider plugins register the optional out-of-process CLI adapters. Most discover an interpreter through PATH or an explicit environment variable:
Python.CPythonfor.py(SALAMATRIX_PYTHON, thenpython.exe/python3.exe);PowerShellfor.ps1(SALAMATRIX_POWERSHELL, thenpwsh.exe/PHP.CLIfor.php(SALAMATRIX_PHP, thenphp.exe);JavaScript.Nodefor.js/.mjs(SALAMATRIX_NODE, thennode.exe/node),Luafor.lua(SALAMATRIX_LUA, then the bundled vcpkg-built
powershell.exe);
with legacy Windows JScript remaining the compatibility fallback for .js;
runtime\lua.exe, then lua.exe/lua55.exe/lua54.exe on PATH).
The process adapter uses a non-shell CreateProcessW invocation, passes the entry point as a quoted file argument, drains a combined stdout/stderr pipe, limits captured output to 1 MiB, terminates timed-out children, and returns the exit code. This makes Python/PowerShell/PHP/Node/Lua real selectable runtimes today; the shared worker bootstrap exposes the common Salamander object model to the modern process adapters. The persistent session seam is now present, and manifest activation starts a bounded host pump thread so worker stdout is processed during normal plugin operation. The Automation host dispatcher also handles the first language-neutral calls (runtime.ready, command execution, active-tab snapshots, string storage, event subscribe/unsubscribe, and a message-box dialog) without sharing native pointers. Richer UI/value bindings are implemented on top of the same boundary, including the shared dialog and control model, progress variants, panel-tab activation/path/refresh, and clipboard/file-picker helpers. Because the worker pump is not Salamander's UI thread, the host dispatch now synchronously marshals calls through CSalamanderGeneralAbstract::InvokeOnMainThread; the callback context remains valid until the main-thread operation completes. Queued long-lived worker lifecycle rules and richer value bindings remain future extensions.
The first worker-transport slice is declared in src/plugins/salamatrix/salamatrix_runtime_protocol.h. It provides a bounded, incremental SMX1 line codec with typed message kinds (hello, ready, call, result, event, shutdown, error), decimal request ids, and compact JSON payloads. The codec rejects embedded newlines, malformed ids, and frames over 1 MiB, and is intentionally independent of any language's JSON library. The Automation bridge now answers the first host calls through this transport; modern process adapters share the standard worker bootstrap; richer value bindings and queued callback/reentrancy policy remain. Host callbacks for registered extensions are protected by owner-aware unload leases.
Process adapters can now opt into the common worker bootstrap with RuntimeExecutionFlagUseWorkerBootstrap. The standalone runtime providers ship the Python, PowerShell, PHP, Node, and Lua bootstraps beside their own .SPL; they perform the SMX1 handshake, expose the same logical Salamander object model, route host calls, and keep an event loop alive after the extension entry point returns. SALAMATRIX_WORKER_ROOT is an explicit deployment/test override; each provider build copies its worker beside that provider binary. Workers can query the same broker without a runtime-specific host extension: Salamander.runtimes.list() in Python, Salamander.runtimes.List() in PowerShell, Salamander->runtimes->list() in PHP, and Salamander.runtimes.list() in Node and Lua call salamander.runtimes.list. The response contains each adapter's id, display name, language, entry-point extensions, version, and current availability, so an extension or the AI assistant can explain a missing interpreter before it tries to execute or package a script.
The legacy ActiveScript registrations remain tied to the Automation bridge refresh/release lifecycle. New language runtimes are intended to be separate provider plugins instead: a PythonRuntime.SPL, PowerShellRuntime.SPL, PHPRuntime.SPL, JavaScriptRuntime.SPL, or LuaRuntime.SPL queries the already registered Salamatrix.Runtime service during SalamanderPluginEntry, registers only its own adapter objects, and unregisters those exact objects during Release. The provider plugin contains the interpreter discovery policy and worker bootstrap, but it does not provide a second UI/command framework and it does not require Automation to be loaded. Native plugins and Automation both query the same broker. Before unregistering, each provider verifies that the same broker is still published by the host so unload cannot turn cleanup into a call through a stale service pointer.
Automation now registers only its legacy ActiveScript adapters. The separate PythonRuntime.SPL, PowerShellRuntime.SPL, PHPRuntime.SPL, and JavaScriptRuntime.SPL own their adapters and worker assets. The public IRuntimeService::RegisterAdapter/UnregisterAdapter contract already permits that split without changing consumers.
The framework's host-service registration is transactional. If any of the ten services cannot be registered, services registered earlier in the attempt are rolled back in reverse order instead of leaving dangling partial registrations.
Salamatrix.Sides and panel-tab snapshots
The runtime-neutral side and tab contract is declared in:
src/plugins/salamatrix/salamatrix_sides.h
ISidesService resolves the logical Left, Right, Source, and Target references to physical sides. It exposes tab counts, active-tab snapshots, lookup by a stable process-local tab id, path reads, tab activation, active-tab path changes, and bounded item-context snapshots (Path, SelectedItems, and FocusedItem). Tab snapshots include physical side, index, path type, and flags for active-on-side, source, target, locked, and detached state.
The core SDK additions in CSalamanderGeneralAbstract expose only value snapshots and opaque ids. They do not leak CFilesWindow or tab-array pointers across the plugin boundary. Each CFilesWindow receives a nonzero monotonically increasing id for its lifetime. Callers must therefore re-read a tab by id before each operation and treat a failed lookup as a stale handle.
Automation publishes the same model under the Salamander.Sides root:
var source = Salamander.Sides.Source;
var tab = source.ActiveTab;
Salamander.TraceI(
source.Name + ": " + tab.Path + " (" + tab.Id + ")");
tab.Activate(true);
Salamander.Sides.Target.Path = "C:\\Temp";
The tab id is exposed to scripts as a decimal string, not a JavaScript number, so a 64-bit native id cannot lose precision in legacy JScript. The current service deliberately stops at tab identity, state, path, and activation. Focused/selected item snapshots, view settings, refresh, and tab lifecycle events remain follow-up work.
Salamatrix.Storage per-extension persistence
The runtime-neutral persistent storage contract is declared in:
src/plugins/salamatrix/salamatrix_storage.h
IStorageService accepts a validated manifest extension id and a key, keeping each extension in a separate namespace. Version 1 supports UTF-8 strings up to 16 KiB, signed 64-bit integers, booleans, deletion, and clearing one namespace. The service is synchronized so future worker-process/runtime bridges do not need to share Automation's old global VARIANT container.
SALAMATRIX.SPL owns the storage and persists all namespaces as one bounded, versioned binary configuration blob through CSalamanderRegistryAbstract. Loading validates the complete header, entry count, lengths, identifiers, types, payload sizes, embedded NULs, and duplicate records before accepting values. Unchanged data is not rewritten.
Manifest-based Automation extensions use the same service through:
var previous = Salamander.Storage.get("lastPath", "(first run)");
Salamander.Storage.set("lastPath", Salamander.Sides.Source.Path);
Salamander.TraceI(Salamander.Storage.Namespace + ": " + previous);
Salamander.Storage is deliberately unavailable to loose legacy scripts with no manifest id, because silently returning to one shared global namespace would break the isolation guarantee. The older SetPersistentVal and GetPersistentVal methods remain as compatibility APIs. Future package management still needs quotas, migration hooks, settings schemas, and an uninstall retention/deletion policy.
Salamatrix.Events host subscriptions
The event contract is declared in:
src/plugins/salamatrix/salamatrix_events.h
IEventsService provides versioned subscribe/unsubscribe with opaque 64-bit subscription ids. Dispatch snapshots matching subscribers under a short lock, releases the lock before invoking callbacks, and therefore permits a callback to unsubscribe itself safely. The current payload contains the event name, host parameter, active physical panel, active tab id, path type, and a copied path. The initial host mapping covers startup/shutdown, settings and configuration changes, color changes, panel swaps, and active-panel changes. Successful operations through the shared Sides API additionally publish sidePathChanged, sideSelectionChanged, sideTabChanged, and sideRefreshed. The core now also forwards pathChanged, selectionChanged, and tabChanged notifications through the same channel. These core events can also be observed when a host operation causes the corresponding change; the side* names identify the operation-level notifications. The payload includes the affected physical side, active tab id, path type, and current path when available.
The Automation facade is:
var subscription = Salamander.Events.subscribe(
"activePanelChanged",
function (name, parameter, path, activeTabId) {
Salamander.TraceI(name + ": " + path);
});
Salamander.Events.unsubscribe(subscription);
Callbacks are owned by the subscribing extension object and are automatically unsubscribed when that object is released. Persistent extension instances and worker-runtime dispatch queues still need to be added; one-shot legacy scripts cannot receive an event after their execution has ended.
Salamatrix.Extensions lifecycle registry
The lifecycle registry is declared in:
src/plugins/salamatrix/salamatrix_extensions.h
IExtensionsService keeps a bounded, owner-aware catalog of valid extension descriptors. Entries have explicit discovered, activating, active, deactivating, inactive, and failed states and can be activated or deactivated through a callback that runs outside the registry lock. Automation registers valid manifest-backed scripts during initial load and refresh, using the CScriptInfo address as the owner; removed scripts are unregistered before their objects are deleted. Duplicate ids from another owner are rejected. Version 1.4 adds descriptor flag metadata for declarative menu-command, Viewer, and File System contributions. The flags do not change the descriptor layout; generic management UI can therefore describe package functions without parsing extension.json or depending on a particular runtime provider.
Automation registrations carry a lifecycle callback. Activating a manifest looks up its selected runtime adapter and starts an IRuntimeSession; the session is retained by the owning CScriptInfo and is stopped before that script is removed. Legacy ActiveScript adapters deliberately reject persistent activation, while the new CLI adapters require a worker-compatible entry point and fail activation if the child exits immediately. Command ownership, host call dispatch, and owner-aware unload leases build on this seam. A runtime host callback acquires a short lease while it touches the extension; unregister waits for those leases after deactivation and rejects new calls once unloading starts.
Service lookup API
The natural integration point is the existing plugin host interfaces. Plugins already receive a CSalamanderPluginEntryAbstract in SalamanderPluginEntry() and use it to obtain core services such as the general, debug, and GUI interfaces. The service lookup should therefore be exposed either from the plugin entry interface or from CSalamanderGeneralAbstract.
Implemented MVP shape:
struct CSalamanderServiceQuery
{
const char* ServiceId;
DWORD MinimumVersion;
DWORD Flags;
};
struct CSalamanderServiceResult
{
void* Interface;
DWORD Version;
const char* ProviderName;
};
virtual BOOL WINAPI QueryService(
const CSalamanderServiceQuery* query,
CSalamanderServiceResult* result) = 0;
The MVP also adds UnregisterService(serviceId, serviceInterface) so temporary PoC providers can safely remove stack-owned service instances before returning from DemoPlug command handlers.
The core-facing MVP is now implemented on CSalamanderGeneralAbstract and backed by a process-local registry in CSalamanderGeneral. Runtime::RuntimeServices registers the PoC UI, Commands, FileOperations, Runtime broker, Automation, Sides, Events, Extensions, AI, and Storage services with both its local registry and the host registry while the runtime object is alive.
Provider registration
Automation Framework providers should register services during their plugin entry/init phase, after their own version check and language/resource initialization succeed.
Implemented MVP shape:
virtual BOOL WINAPI RegisterService(
const char* serviceId,
DWORD version,
void* serviceInterface,
const char* providerName) = 0;
virtual BOOL WINAPI UnregisterService(
const char* serviceId,
void* serviceInterface) = 0;
The registry must reject duplicate providers for the same service/version unless a future policy explicitly supports replacement. The provider plugin must not be unloaded while a registered service may still be queried or retained by other plugins.
Missing runtime behavior
Native plugin callers
If a native plugin requests a service that is not installed, not loaded, or too old, QueryService should return failure and set the output interface pointer to NULL. The caller remains responsible for deciding whether this is optional or fatal.
Recommended helper behavior for required services:
CSalamanderServiceQuery query;
memset(&query, 0, sizeof(query));
query.ServiceId = SALAMATRIX_SERVICE_UI;
query.MinimumVersion = SALAMATRIX_UI_VERSION_1_0;
CSalamanderServiceResult result;
memset(&result, 0, sizeof(result));
if (!SalamanderGeneral->QueryService(&query, &result) || result.Interface == NULL)
{
SalamanderGeneral->SalMessageBox(
parent,
"This plugin requires Salamatrix UI Framework.",
pluginName,
MB_OK | MB_ICONERROR);
return FALSE;
}
Script callers
Script runtimes should translate missing required services into clear runtime exceptions. Recommended wording:
This script requires Salamatrix UI Framework 1.0 or newer. Install or enable the
Salamatrix Framework plugin and try again.
The exception should include:
- service identifier,
- requested version,
- currently available version, if any,
- plugin/runtime that requested it.
UI behavior
When a user invokes an extension whose required service is missing, the UI should show a localized message in this form:
This extension requires Salamatrix UI Framework.
Install or enable Salamatrix Framework and try again.
If the Plugin Manager supports Automation Framework classification, Salamatrix should be shown as a runtime/service component rather than as an ordinary menu extension.
Integration with existing plugin registration
The current plugin model already has a natural lifecycle for service providers:
1. Salamander loads a plugin and calls SalamanderPluginEntry(). 2. The plugin obtains CSalamanderGeneralAbstract, debug, and GUI interfaces. 3. The plugin verifies SalamanderPluginGetReqVer() compatibility. 4. The plugin loads language resources and initializes WinLib if needed. 5. The plugin calls SetBasicPluginData() to declare its basic metadata and functions. 6. The plugin returns its CPluginInterfaceAbstract implementation.
Salamatrix should register its services after step 4 and before returning the plugin interface. Consumers should query services after their own basic startup checks have completed.
The core plugin manager now owns the service registry near the code that tracks loaded plugins and their metadata. Each provider can register an opaque owner; consumers acquire a short lease around calls through the borrowed interface. Unregister marks the service as unloading, rejects new acquisitions, waits for active leases, and only then removes the record. This keeps service ownership tied to the same objects that already control plugin lifetime and unload checks.
Recommended implementation direction:
- service registry storage and synchronization live in the core plugin manager,
RegisterServiceOwned/UnregisterServiceOwnedexpose provider ownership,QueryServiceplusAcquireService/ReleaseServiceexpose borrowed services- unload blocks removal while exported services are still held,
- persist only plugin installation metadata, not live service pointers.
with an explicit consumer lease through CSalamanderGeneralAbstract,
Salamatrix.UI native dialogs and progress
The first concrete object API in Salamatrix.UI is the progress dialog. The C++ MVP surface is declared in:
src/plugins/salamatrix/salamatrix_ui.h
The declaration intentionally wraps the existing CSalamanderForOperationsAbstract progress methods instead of duplicating progress-window behavior. This keeps the first Salamatrix UI object compatible with current native plugin operations and with the existing Automation progress object.
The initial API shape contains:
Salamatrix::UI::ProgressDialogOptionsfor title, parent window, one/two-barSalamatrix::UI::IProgressDialogas the ABI-oriented object contract.Salamatrix::UI::ProgressDialogas the first C++ adapter overSalamatrix::UI::IUIServiceas the future service returned by
mode, file/total labeling, and initial Cancel state.
CSalamanderForOperationsAbstract.
QueryService("Salamatrix.UI", SALAMATRIX_UI_VERSION_1_0, ...).
The object covers the MVP lifecycle and control flow:
1. SetTitle(...) sets the title before the dialog is opened. 2. Open() or Open(options) creates the existing Salamander progress dialog. 3. SetTotal(...) and SetTotals(...) configure one-bar or two-bar totals. 4. AddText(...) appends progress log/status text. 5. Step(...), SetPosition(...), and SetPositions(...) update progress and return whether the operation should continue. 6. IsCancelled() polls cancellation by refreshing the existing progress dialog with a zero-sized progress update. 7. SetCancelEnabled(FALSE) supports cleanup phases where Cancel must be disabled. 8. Close() closes the dialog explicitly, and the C++ adapter destructor closes it as a final safety net.
The first script mapping is now exposed through the Salamander root object and is backed by Salamatrix.UI internally:
var progress = Salamander.UI.progress("Processing files");
progress.Maximum = files.length;
progress.Show();
try {
for (var i = 0; i < files.length; ++i) {
progress.AddText(files[i].Name);
progress.Position = i + 1;
if (progress.IsCancelled) {
progress.CanCancel = false;
break;
}
}
}
finally {
progress.Hide();
}
Existing Automation scripts may keep using Salamander.ProgressDialog; the new Salamander.UI.progress(...) path returns the same progress Automation interface but is backed by Salamatrix.UI through the Automation bridge.
The service has since grown into the shared native dialog framework. Its append-only 1.4 surface exposes IDialog, IControl, DialogOptions, ControlOptions, and ControlLayout. The concrete NativeDialog remains in Salamatrix and covers the complete host-control gallery used by DemoPlug:
- labels, host static text, text boxes, check boxes, and radio buttons;
- combo boxes, ListView, TreeView, and TabControl item binding;
- ordinary, arrow, text-arrow, and color-arrow buttons;
- editable native folder and file pickers;
- host hyperlinks, progress bars, group boxes, and toolbar headers;
- explicit bounds, validation, change events, tooltips, hyperlink actions,
style flags, colors, known/indeterminate progress, and header button masks.
Manifest extensions call the framework-owned package dispatcher through the salamander.ui.dialog.* SMX1 vocabulary. Dialog records are scoped to their owning package and are released on close, deactivation, or package removal. This dispatcher is part of Salamatrix; it is not supplied by Automation. Automation JScript has a thin COM dialog facade, and native plugins negotiate SALAMATRIX_UI_VERSION_1_4 and use the same C++ objects directly.
The Node, Python, PowerShell, PHP, Lua, Automation JScript, and DemoPlug demos each construct the same 463 x 236 Salamatrix UI capabilities window from their own source. A Created by group identifies the runtime and extension, making the window both a control gallery and an executable construction example. The five worker facades forward the same extended control properties; salamander.host.uptime supplies the common system-uptime label without language-specific platform code.
NativeDialog applies the Salamander-selected Windows Dark Mode (experimental) color scheme, not the Windows application-mode preference. It also owns DPI scaling, dialog font, initial focus, tab traversal, and bounded accessibility tooltip metadata. See Salamatrix.UI framework and custom dialog guide for the complete control/property table and examples in every supported language.
Salamatrix.Commands and FileOperations MVP
The command and interactive file-operation MVP surface is declared in:
src/plugins/salamatrix/salamatrix_commands.h
Salamatrix.Commands is intentionally a thin layer over existing Salamander commands. It maps public command names such as QuickRename, Copy, and Move to the existing SALCMD_QUICKRENAME, SALCMD_COPY, and SALCMD_MOVE command identifiers and posts them through CSalamanderGeneralAbstract::PostSalamanderCommand. Before posting, the MVP implementation can check GetSalamanderCommand so that disabled commands fail instead of opening inconsistent UI.
Salamatrix.FileOperations uses the same command path for its interactive MVP:
RenameInteractive(...)opens the existing Quick Rename workflow.CopyInteractive(...)opens the existing Copy dialog/workflow.MoveInteractive(...)opens the existing Move/Rename dialog/workflow.
The file-operation MVP must not clone .rc dialog resources or reimplement the copy/move/rename validation logic. It should route to the command handlers that already use the localized strings, histories, target-directory helpers, operation mask handling, and panel/file-system integration. This keeps the behavior aligned with the documented Quick Rename, Copy, and Move/Rename dialog boxes.
The shared return enum is Salamatrix::Runtime::OperationResult:
OperationResultOk
OperationResultCancel
OperationResultError
OperationResultNotAvailable
For the command-posting MVP, OperationResultOk means the existing command was accepted and posted, OperationResultNotAvailable means the command exists but is disabled in the current panel context (for example Quick Rename without a focused/selected item), and OperationResultError means the command was unknown or no command service was available. OperationResultCancel is reserved for the next synchronous/modal integration step where a direct workflow wrapper can observe the dialog result.
Implemented script mapping:
Salamander.Commands.execute("QuickRename") // returns "ok", "cancel", "not_available", or "error"
Salamander.FileOperations.rename_interactive() // returns "ok", "cancel", "not_available", or "error"
Salamander.FileOperations.copy_interactive() // returns "ok", "cancel", "not_available", or "error"
Salamander.FileOperations.move_interactive() // returns "ok", "cancel", "not_available", or "error"
Missing Salamatrix runtime services are converted by the Automation wrapper to a readable script exception: This script requires Salamatrix Framework to be installed and loaded.
Salamatrix Automation adapter MVP
The first script/Automation adapter contract is declared in:
src/plugins/salamatrix/salamatrix_automation.h
The adapter is deliberately thin: it owns no dialog resources and implements no parallel UI behavior. It receives native Salamatrix.UI, Salamatrix.Commands, and Salamatrix.FileOperations services and exposes script-shaped wrapper objects over them. This keeps the Automation/COM layer as an adapter instead of a second UI framework.
MVP script facade mapping:
Salamander.UI.progress(...) -> Salamatrix::Automation::ScriptUIAdapter
Salamander.UI.dialog(...) -> Salamatrix::Automation::ScriptUIAdapter::Dialog
Salamander.Commands.execute(...) -> Salamatrix::Automation::ScriptCommandsAdapter
Salamander.FileOperations.*_interactive -> Salamatrix::Automation::ScriptFileOperationsAdapter
Salamander.FileOperations.delete/create_directory/refresh/properties
-> same native service
ScriptProgressDialog creates a native Salamatrix::UI::IProgressDialog through IUIService, opens it with the supplied title, delegates total, add_text, step, is_cancelled, and cancel_enabled, then closes and destroys the native progress object when the script wrapper is destroyed. Existing Automation Salamander.ProgressDialog can remain as a compatibility facade and later be implemented on top of the same IUIService.
ScriptUIAdapter::Dialog creates the shared Salamatrix::UI::IDialog, adds native controls, shows it modally, and exposes the same control state to the caller. This is the in-process counterpart of the SMX1 worker dialog calls.
ScriptCommandsAdapter::Execute(...) delegates to ICommandService::Execute(...) so script calls such as Salamander.Commands.execute("QuickRename") still use the existing Salamander command handlers. ScriptFileOperationsAdapter delegates rename_interactive, copy_interactive, move_interactive, delete, create_directory, refresh, and properties to IFileOperationsService, which posts the corresponding existing Salamander command and preserves its normal native dialogs and enablement rules.
The shared UI contract now includes a native dialog/control model in salamatrix_ui.h/.cpp. It is intentionally small but real: native plugins and Automation workers use the same dialog object and control state:
Dialog-> sharedSalamatrix::UI::IDialog(legacyIDialogAdapterremains a compatibility shape)Container->IContainerAdapter/shared dialog control collectionLabel->ControlKindLabelTextBox->ControlKindTextBoxCheckBox->ControlKindCheckBoxComboBox->ControlKindComboBoxButton->ControlKindButtonRadioButton->ControlKindRadioButtonListView->ControlKindListView(native common-control surface with item binding)TreeView->ControlKindTreeView(native common-control surface with node/parent binding)TabControl->ControlKindTabControl(native common-control surface with tab item binding)
The current Automation GUI layer can now be migrated incrementally to these interfaces instead of creating a second runtime-specific UI. The native implementation renders labels, text boxes, check/radio buttons, combo boxes, buttons, and the native ListView/TreeView/TabControl common-control surfaces. Runtime workers can add and clear items before showing a dialog; TreeView items accept a parent index, while ListView columns and selected indexes are exposed through the same dialog object. Richer selection notifications and virtualized data binding remain a follow-up.
Salamatrix.AI provider seam
src/plugins/salamatrix/salamatrix_ai.h defines a provider-neutral assistant contract. The separately installable SalamatrixAI.SPL registers local.command when SALAMATRIX_AI_COMMAND is configured and can also register a native local.ollama provider when SALAMATRIX_AI_MODEL and a local endpoint are configured; Automation remains a consumer of the service. The command receives one UTF-8 JSON request on standard input and returns one JSON object/array on standard output; the bridge bounds output to 1 MiB and clamps generation to a two-minute timeout. The native provider uses the same structured contract over WinHTTP, so a local model can be used without shipping a model SDK or coupling Salamander to a vendor. The optional companion plugin Salamatrix AI Local LLaMA registers local.bundled, a server-free provider that starts runtime\\llama-cli.exe against the model selected on the plugin Configuration page. Qwen2.5-Coder 1.5B Instruct Q4_K_M is the recommended default; Qwen2.5-Coder 0.5B Instruct Q4_K_M remains available as a lightweight option for English prompts only. Both models may be installed side by side and the selection is persisted. The installer downloads the pinned Windows x64 CPU package and selected Qwen GGUF file, verifies both SHA-256 values, and stores the applicable license notices beside the files. Existing runtime\\salamatrix.gguf installations are migrated as the 0.5B profile. Both assets can be overridden with SALAMATRIX_AI_BUNDLED_COMMAND and SALAMATRIX_AI_BUNDLED_MODEL; the provider is advertised as ready only when both files exist. The chat automatically uses the first available provider, or an explicit SALAMATRIX_AI_PROVIDER such as local.bundled, local.ollama, or local.command when set. The standalone chat also enumerates currently available providers and lets the user choose one explicitly; auto retains resilient fallback behavior. When the chat is opened from a Salamander operation menu, its Run action also passes that borrowed operation context through Salamatrix.ScriptRunner, so generated workers can create and update the same host-owned progress dialog as ordinary extensions; a standalone chat has no implicit operation context. The shared service validates the structured assistant contract (title, description, capabilities, estimatedEffects, and script) and exposes the parsed effect flags to callers. Workers expose both ai.generate(...) and ai.preview(...); preview adds a conservative canRun safety result (shared by native clients through Salamatrix::AI::IsSafeToRun). Automation now includes an Ask-AI menu action that gathers source-panel context, shows a native preview summary, copies the generated script for explicit review, offers an explicit Run only after user confirmation when the response names an available runtime, and offers an explicit Save As file picker. When a supported runtime is present, the same workflow can also create a manifest-validated extension.json plus entry-point package under a user-selected folder. Both calls accept an optional runtime hint, existing script, and repair feedback, so a configured local model can target Python, PowerShell, PHP, or another registered adapter and continue a bounded conversation without a second provider-specific API. The native Ask-AI action offers at most three generation iterations before the final preview, keeping the repair loop bounded. The main AI helper remains model-free; the optional Salamatrix AI Local LLaMA companion supplies the on-demand downloader for the separately installed llama.cpp binary and GGUF model. For a local model without a custom provider implementation, the repository also ships the optional src/plugins/automation/runtime/salamatrix_ai_local.py command wrapper. It speaks Ollama's local /api/generate protocol by default and can switch to an OpenAI/llama.cpp-compatible /v1/chat/completions endpoint with SALAMATRIX_AI_PROTOCOL=chat-completions. The native provider accepts SALAMATRIX_AI_OLLAMA_URL (or the compatibility alias SALAMATRIX_AI_HTTP_URL) for Ollama, and SALAMATRIX_AI_LLAMA_URL for a llama.cpp/OpenAI-compatible endpoint; a bare host URL automatically receives the matching default path. SALAMATRIX_AI_MODEL (or SALAMATRIX_AI_OLLAMA_MODEL) selects the model. The wrapper is never launched unless it is selected through SALAMATRIX_AI_COMMAND, and its own timeout is capped below the host's two-minute provider limit.
Salamatrix PoC runtime wiring
The first in-process proof-of-concept wiring is declared in:
src/plugins/salamatrix/salamatrix_poc.h
This PoC is intentionally small and can run either against the real Salamatrix runtime plugin or against a local fallback service aggregate. The runtime plugin lives in src/plugins/salamatrix/, has a standalone Visual Studio project in src/plugins/salamatrix/vcxproj/, and exports SALAMATRIX.SPL; on plugin entry it creates a persistent Runtime::RuntimeServices instance and registers Salamatrix.UI, Salamatrix.Commands, Salamatrix.FileOperations, and the Runtime broker, Automation adapter, Sides, Events, Extensions, AI, and Storage services in Salamander's core-facing CSalamanderGeneralAbstract service registry. DemoPlug remains only a consumer/sample and no longer needs to act as the long-lived provider.
Runtime::ServiceRegistryprovides a fixed-size local registry with ownedRuntime::LocalUIServiceimplementsSalamatrix::UI::IUIServiceby creatingRuntime::RuntimeServiceswires one in-process UI service, command service,RunProgressDialogPoc(...)opens a native Salamatrix progress dialog, sets aRunAutomationProgressPoc(...)exercises the script-facingExecuteQuickRenamePoc(...)andCopyInteractivePoc(...)prove that the
providers and consumer leases, while CSalamanderGeneral provides the synchronized core-facing registry used by plugin consumers.
and destroying Salamatrix::UI::ProgressDialog objects over the existing CSalamanderForOperationsAbstract progress API.
file-operation service, runtime broker, script root adapter, Sides, Events, Extensions, Storage, and service registry together. The Salamatrix runtime plugin owns one persistent instance and unregisters the host services when the plugin is released.
total, adds text, steps progress, detects Cancel, disables Cancel for cleanup, and closes the dialog.
Salamander.UI.progress(...) shape through ScriptUIAdapter.
Commands/FileOperations MVP can route to existing Salamander command workflows.
The first consumer/sample integration point is a DemoPlug menu submenu named Salamatrix PoC. It exposes a Run All PoC summary command, a progress PoC command, a Quick Rename command PoC, and a Copy dialog PoC. The individual menu entries are always enabled; for these PoC menu commands the adapter bypasses panel enabler checks so the summary reports whether the existing command was accepted/posted rather than whether the current panel context has a focused or selected item. The progress command calls the native progress PoC and the script-facing progress adapter PoC from CPluginInterfaceForMenuExt::ExecuteMenuItem, while the Quick Rename and Copy entries route through the Commands/FileOperations adapters. When SALAMATRIX.SPL is installed and loaded, the sample queries the host registry first and uses the registered runtime services; when the runtime plugin is missing, the PoC keeps a local fallback so the demo remains runnable.
The Automation plugin is the first non-demo consumer bridge. Its CAutomationSalamatrixBridge queries CSalamanderGeneral::QueryService for the framework-provided Salamatrix.Automation, Salamatrix.UI, Salamatrix.Commands, Salamatrix.FileOperations, Salamatrix.Runtime, and Salamatrix.Sides, Salamatrix.Events, Salamatrix.Extensions, and Salamatrix.Storage services when the plugin connects and immediately before script execution. It does not create a local fallback framework provider, so the Automation layer remains an adapter/consumer of SALAMATRIX.SPL rather than another provider of duplicated UI or command logic.
Automation 2.0 exposes that bridge to scripts through Salamander.UI, Salamander.Commands, Salamander.FileOperations, Salamander.Sides, Salamander.Events, and Salamander.Storage. The first script-facing UI object is Salamander.UI.progress(...), returning the existing progress Automation interface backed by Salamatrix.UI. Commands and interactive file operations return textual MVP results: ok, cancel, not_available, or error. Sides and tabs are COM/IDispatch wrappers over the same runtime-neutral snapshots and opaque ids used by native callers; Storage is bound to the executing manifest's extension id.
The initial command catalog is deliberately small and stable: QuickRename, Copy, Move, and MoveRename, with script aliases quick_rename, copy, move, and move_rename. All MVP entries route to existing Salamander command handlers and require the current panel context to make the command meaningful.
For the first real scripted-extension sample, Automation recognizes two MVP registration styles while discovering scripts in the existing script repository.
Inline script metadata remains supported for tiny single-file samples:
// Salamatrix.CommandId: Salamatrix.ProgressDemo
// Salamatrix.CommandTitle: Salamatrix Progress Demo
// Salamatrix.CommandMenu: both
// Salamatrix.CommandContextMenu: true
// Salamatrix.CommandRequires: file
Manifest-based extensions use an extension.json file in the extension root. The entry point may be next to it or in a safe relative subdirectory:
{
"id": "Salamatrix.ProgressDemo",
"name": "Salamatrix Progress Demo",
"runtime": "Automation.JScript",
"entryPoint": "main.js",
"commands": [
{
"id": "Salamatrix.ProgressDemo",
"title": "Salamatrix Progress Demo",
"menu": "both",
"contextMenu": true,
"requires": "file"
}
]
}
The reader is a strict UTF-8 JSON parser rather than a scalar text scanner. It rejects malformed JSON, duplicate members, invalid UTF-8 and Unicode surrogate pairs, unsafe absolute or traversing entry points, unsupported schema versions, invalid field types, duplicate command ids, and unsupported placement/context values. Manifests are limited to 1 MiB and nesting depth 64.
Schema version 1 supports:
- package
id,name/title,version,description,web, optional - a runtime string or
{ "id", "minimumVersion" }object; - a validated string array of declared
capabilities; - up to 64 command records with
id,title,handler,menu/placement,
security disclosure (networkAccess, externalProcesses, scriptExecution, activeWebContent, elevation), and entryPoint;
contextMenu, toolbar, toolbarMenu, and requires. A command with both toolbar and toolbarMenu contributes one Extension Bar button that opens the package's visible plugin-menu commands instead of executing directly.
Automation currently contributes the first parsed command to its legacy native menu surface; preserving all parsed commands for dynamic command contribution is the next command-service step. Unlike the old scanner, discovery is not limited to extensions with an IActiveScript file association. A valid manifest entry point is discovered first, then execution resolves the exact runtime id and minimum version through Salamatrix.Runtime. Missing or incompatible runtimes produce an explicit error instead of silently failing engine lookup.
MVP requires values map to Salamander menu event masks as follows:
any: no special context; enabled byMENU_EVENT_TRUE.disk: requires a disk panel context.focused: requires a focused file or directory on disk.file: requires a focused file or selected files on disk.selection: requires selected files or directories on disk.
When a manifest declares a non-empty capabilities array, Automation also enforces the list at the persistent SMX1 host boundary. Calls are grouped into panels.read, panels.write, ui.dialogs, commands, file-operations, storage, events, and ai; a denied call returns a structured capability denied error. Persistent runtime event frames use a bounded session queue so host callbacks do not synchronously block the Salamander UI on a worker pipe. Scripts without a declared list retain legacy compatibility until the user-facing grant/revocation policy is available.
MVP acceptance criteria
The platform skeleton is ready when:
1. The runtime component name and source location are documented as Salamatrix in src/plugins/salamatrix/. 2. The active MVP subsystem names are defined as Salamatrix.UI, Salamatrix.Commands, Salamatrix.FileOperations, and Salamatrix.Runtime, Salamatrix.Sides, Salamatrix.Events, Salamatrix.Extensions, and Salamatrix.Storage. 3. A versioned QueryService/RegisterService ABI shape is documented. 4. Missing-runtime behavior is defined for native plugins, scripts, and UI. 5. The intended integration point with existing plugin registration and plugin lifetime management is documented. 6. The first Salamatrix.UI progress dialog contract exists and wraps the current CSalamanderForOperationsAbstract progress implementation. 7. The first Salamatrix.Commands and Salamatrix.FileOperations contracts exist and route their MVP interactive behavior through existing Salamander command/workflow entry points. 8. The first Salamatrix.Automation adapter contract exposes script-shaped wrappers over the native UI, Commands, FileOperations, Sides, Events, and Storage MVP services. 9. The generic form-builder model is reserved as adapter contracts only; no duplicate Automation UI implementation is introduced outside Salamatrix.UI. 10. The in-process Salamatrix PoC wires UI, Commands, FileOperations, Runtime, Sides, Events, Extensions, Storage, Automation adapters, an owned local service registry, and the core-facing leased CSalamanderGeneral service registry together and is exposed as a DemoPlug Salamatrix PoC menu sample. 11. SALAMATRIX.SPL exists as the first Automation Framework provider plugin, creates the persistent Runtime::RuntimeServices aggregate, registers the MVP services with CSalamanderGeneral under its owner token, and unregisters them during plugin release after active service leases drain. 12. The Automation plugin contains a consumer-only bridge that refreshes and caches host-registered Salamatrix services instead of instantiating a local duplicate runtime. 13. Automation 2.0 exposes Salamander.UI, Salamander.Commands, and Salamander.FileOperations backed by the Salamatrix runtime bridge. 14. Script discovery supports command-registration metadata for multiple commands and a minimal extension.json manifest for the sample scripted extension; the Automation menu publishes every registered command. 15. Scripted-extension command placement controls plugin-menu vs context-menu visibility and applies MVP requires context masks to Automation menu items. 16. Salamatrix.Runtime is a registered broker service with versioned adapter descriptors, registration, enumeration, and lookup. 17. Automation advertises available legacy Active Scripting engines as compatibility runtime adapters and unregisters them during release. 18. Partial host-service registration is rolled back transactionally. 19. Runtime adapters expose structured execution requests/results; manifest execution is selected by runtime id and minimum version through the broker. 20. extension.json is parsed as strict UTF-8 JSON with schema, path, runtime, capability, and command validation. 21. Manifest entry points are discovered independently of legacy ActiveScript file associations. 22. Salamatrix.Sides exposes Left, Right, Source, and Target resolution plus safe panel-tab snapshots, opaque ids, paths, activation, and active-tab path changes without leaking core implementation pointers. 23. Automation exposes the same model through Salamander.Sides, including precision-safe string tab ids and stale-handle checks. 24. The plugin SDK version is raised to 105 for the appended service-registry and panel-tab virtual methods. 25. Salamatrix.Storage provides synchronized per-extension namespaces for UTF-8 strings, signed 64-bit integers, and booleans, with versioned configuration persistence. 26. Automation exposes the current manifest namespace through Salamander.Storage; loose scripts without an extension id cannot enter a shared fallback namespace. 27. Standalone /W4 /WX tests cover namespace isolation, types, limits, mutation, configuration round-trip, unchanged-save behavior, and corrupt blob rejection. 28. Salamatrix.Events provides unsubscribe-safe host subscriptions for the initial lifecycle, settings, configuration, color, panel-swap, and active panel events; Automation exposes the same subscription contract. 29. Salamatrix.Extensions provides an owner-aware manifest registry with explicit lifecycle states and safe activate/deactivate transitions; Automation publishes and removes manifest descriptors during script-list load and refresh. 30. Independent runtime .spl providers register Python.CPython, PowerShell, PHP.CLI, JavaScript.Node, and Lua out-of-process adapters when their interpreters are discoverable, with bounded output capture, timeout termination, and structured exit results; Automation consumes the broker without owning these modern registrations. 31. salamatrix_runtime_protocol.h provides bounded incremental SMX1 worker framing with typed lifecycle/call/event messages and standalone limit and malformed-frame tests. 32. IRuntimeSession and StartPersistent() provide bidirectional persistent process sessions; manifest lifecycle activation owns and releases sessions, and a Python echo-worker integration test verifies round-trip framing. 33. The Automation host dispatcher answers runtime.ready, command execution, active-tab snapshots, and per-extension string storage calls over SMX1. 34. Manifest activation starts and joins a host pump thread for each persistent worker, so the dispatcher is active outside of tests as well. 35. The dispatcher supports event subscriptions with pushed event frames and a parented salamander.ui.messageBox call; subscriptions are removed before the worker session is stopped. 36. Python, PowerShell, PHP, and Node process adapters can start their shared worker bootstraps; the process-runtime integration test exercises handshake, commands, storage, event subscription, shared dialogs, and shutdown end to end for Python, PowerShell, and PHP. 37. Salamatrix.AI exposes provider registration; the separately installable SalamatrixAI.SPL supplies the optional bounded local command provider selected by SALAMATRIX_AI_COMMAND, a native Ollama HTTP provider selected by SALAMATRIX_AI_MODEL, and its native Ask/Preview/Run/Save chat with an Auto/explicit provider picker and ready/unavailable provider status. When Automation is loaded, it also publishes Salamatrix.ScriptRunner, so the AI Run action uses the same capability-aware SMX1 host dispatcher as a regular scripted extension instead of a privileged AI-only execution path, including the borrowed operation context required by host progress dialogs when the chat was opened from an operation menu. 38. The shared worker object model exposes Salamander.ui.input_box(...) and the host renders it as a parented native Windows dialog with an editable value; message_box(...) and input_box(...) are routed through the shared Salamatrix.UI service and the same SMX1 host calls are available to Python, PowerShell, PHP, and Node. 39. Salamatrix.UI now publishes a reusable native IDialog/IControl contract and NativeDialog implementation for labels, text boxes, check/radio buttons, combo boxes, buttons, ListView, TreeView, TabControl, group boxes, host static text and hyperlinks, progress bars, arrow/text-arrow/ color-arrow buttons, and toolbar headers; workers can use dialog.addControl(..., layout) for explicit control bounds and the common readOnly, checked, dialogResult, keepOpen, and multiline control options; worker dialog(...) facades also pass explicit native width/height through the same contract; and dialog.setValidation(...) for required-field checks; dialog.onChange(...) receives control-change events through the same SMX1 event channel, and the worker input-box path goes through this service rather than owning a second dialog backend. The JavaScript, Python, PowerShell, PHP, and Lua demos each construct the same explicit capabilities dialog themselves and identify their runtime and extension in its Created by group. The native DemoPlug and Automation JScript demo do the same through their respective public Salamatrix.UI facades; none depends on a prebuilt Automation-owned gallery window. Salamander.ui.uptime() supplies the shared host uptime text without language-specific platform calls. 40. The command catalog covers the available core SALCMD_* operations and IFileOperationsService exposes interactive rename/copy/move/delete, create-directory, refresh, and properties wrappers to native callers and all four modern worker runtimes. 41. Workers can create native dialogs, add all currently supported control kinds (including item/node/tab binding), show them modally, read control state, and destroy them through the same Salamatrix.UI service; the bounded process-runtime tests exercise this path in Python, PowerShell, PHP, and Node. 42. The same shared UI service now exposes UTF-8 open/save file pickers and a native folder picker; Python, PowerShell, PHP, and Node workers call Salamander.ui.pick_file() / PickFile() / pickFile() or pick_folder() / PickFolder() / pickFolder() and receive a structured selected/path result. 43. The shared worker facade exposes runtime discovery through Salamander.runtimes.list() / List() / list(); the host returns each adapter's id, language, entry-point extensions, version, and availability. 44. The optional runtime/salamatrix_ai_local.py command wrapper translates the provider-neutral AI request into Ollama or OpenAI-compatible local endpoint calls while preserving the host's bounded JSON contract. 45. SalamatrixAI.SPL also exposes local.ollama directly over WinHTTP when SALAMATRIX_AI_MODEL and SALAMATRIX_AI_OLLAMA_URL (or SALAMATRIX_AI_HTTP_URL) are configured; setting SALAMATRIX_AI_LLAMA_URL selects the OpenAI/llama.cpp-compatible /v1/chat/completions protocol unless SALAMATRIX_AI_PROTOCOL overrides it. The command wrapper remains available for custom model adapters. 46. The existing core Plugin Manager queries Salamatrix.Extensions and displays manifest-backed extensions alongside .SPL plugins. These rows expose the manifest name, state, version, runtime, entry point, and ID. The Functions detail lists manifest commands as Menu Extension, viewers[] as File Viewer, and fileSystems[] as File System (FS); the existing Test action becomes localized Activate/Deactivate for the selected manifest row. Script packages are still not treated as loadable native plugins; a separate Extension Manager is intentionally not introduced.