Item F (slice-1 fix round). IPluginHost.VtankProfileDirectory handed the plugin a raw string path and told it to fall back to its own System.IO-based portable default when null — a plugin reading and resolving filesystem paths itself, which is exactly the seam the rest of IPluginHost.Storage deliberately avoids (Core.Plugins.ScopedPluginHost scopes/validates every key; the plugin never sees a path). - IPluginHost: VtankProfileDirectory (string?) deleted; new VtankProfiles (IPluginStorage, defaults to NoOpPluginStorage) added — a second, UNSCOPED storage instance (unlike Storage, which Core scopes per plugin manifest id) rooted at a host-composed VTank-compatible directory. - ScopedPluginHost.VtankProfiles forwards _inner.VtankProfiles directly (no scoping — it names one shared external location, not per-plugin data). New PluginSessionTests.ScopedHostForwardsVtankProfilesUnscoped proves the forwarded instance is the exact same object (Assert.Same), not a wrapper. - AppPluginHost/Program.cs: new vtankProfiles constructor parameter, composed as FilePluginStorage(runtimeOptions.VtankProfileDirectoryOverride ?? Path.Combine(applicationPaths.DataDirectory, "vtank")). - RuntimeOptions.VtankProfileDirectoryOverride: new init-only property parsed from ACDREAM_VTANK_PROFILE_DIR (row added to docs/launch-options.md, side-effects column states the redirect is the only effect and documents the NullIfEmpty whitespace-not-special-cased quirk it shares with every other path-override flag). New RuntimeOptionsTests.VtankProfileDirectoryOverrideIsNullUnlessSet. - FilePluginStorage.List(prefix): empty prefix now means "the storage root itself" instead of throwing (Resolve() rejects empty/whitespace keys, which is correct for every OTHER caller but wrong for "list everything" — VtankProfileDirectory needs exactly that). - Headless: HeadlessPluginHost gained the same VtankProfiles property/constructor param, threaded through HeadlessPluginSession.Create -> HeadlessSessionHost -> HeadlessProcessHost, composed from the new HeadlessPathSet.VtankProfilesDirectory (<DataDirectory>/vtank, no ACDREAM_VTANK_PROFILE_DIR-equivalent override — Headless path overrides are HeadlessPathOverrides/CLI flags, not env vars). A small AcDream.Headless.Plugins.FilePluginStorage duplicates the App implementation byte-for-byte (Headless does not reference AcDream.App and no shared "platform plugins" library exists yet to host one copy; documented as a reasonable future consolidation, not required here). - VtankProfileDirectory.cs rewritten: Resolve/PortableDefault deleted outright (no more System.IO, no plugin-owned portable-default fallback); ListSettingsProfiles/ListNavigationProfiles/ListMetaProfiles now take IPluginStorage and enumerate through EnumerateFileNames, which calls storage.List(string.Empty) and skips any key containing '/' (VTank's profile directory is flat; a nested key from some other IPluginStorage implementation is not a profile file). VtankProfileDirectoryTests rewritten against an in-memory IPluginStorage fake instead of real temp directories; new NestedPathKeysAreNotTreatedAsProfileFiles pins that skip. The prior Resolve/PortableDefault-specific tests (Linux-path guarantee, host-override-vs-portable-default) are superseded by RuntimeOptionsTests.VtankProfileDirectoryOverrideIsNullUnlessSet plus the RuntimeOptions.FromEnvironment Path.Combine-only composition in Program.cs. - docs/architecture/acdream-architecture.md: one sentence in the Storage/List(prefix) paragraph naming VtankProfiles as the second, unscoped storage. No production caller of VtankProfileDirectory's listing methods exists yet (A2's foundation is not wired into MossTankProfileStore/ MossTankMetaProfileStore/MossTankRouteProfileStore's own selection — per that slice's own ledger note), so this is a contract + plumbing change with no MossTank runtime behavior change. MossTank suite: 562/562. Core.Tests (Plugin filter): 50/50. App.Tests (Plugin|LaunchOptions|RuntimeOptions filter): 135/135. Headless.Tests: 173/174 (the one failure, HeadlessCredentialResolverTests. LinuxRejectsGroupOrOtherCredentialPermissions, is a pre-existing Linux-only lane gate that throws PlatformNotSupportedException on this Windows host — unrelated to this change). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
63 lines
2.8 KiB
C#
63 lines
2.8 KiB
C#
// src/AcDream.Plugin.Abstractions/IPluginHost.cs
|
|
namespace AcDream.Plugin.Abstractions;
|
|
|
|
/// <summary>
|
|
/// Entry point for a plugin into the acdream runtime. The surface will grow
|
|
/// across phases as more systems come online.
|
|
/// </summary>
|
|
public interface IPluginHost
|
|
{
|
|
/// <summary>
|
|
/// <see langword="true"/> when <see cref="Ui"/> registrations can be
|
|
/// projected by this host. No-window hosts return <see langword="false"/>
|
|
/// and expose <see cref="NoOpUiRegistry.Instance"/> so a plugin may keep
|
|
/// one code path while deliberately omitting presentation work.
|
|
/// </summary>
|
|
bool HasUi { get; }
|
|
|
|
IPluginLogger Log { get; }
|
|
IGameState State { get; }
|
|
IEvents Events { get; }
|
|
ISelectionService Selection { get; }
|
|
IUiRegistry Ui { get; }
|
|
/// <summary>
|
|
/// Locally handled slash/at commands. Hosts without command routing expose
|
|
/// an inert registry so an API-v1 plugin can retain one code path.
|
|
/// </summary>
|
|
IPluginCommandRegistry Commands => NoOpPluginCommandRegistry.Instance;
|
|
/// <summary>
|
|
/// Durable storage scoped by the host to this plugin's manifest id.
|
|
/// No-window/test hosts may explicitly expose the inert implementation.
|
|
/// </summary>
|
|
IPluginStorage Storage => NoOpPluginStorage.Instance;
|
|
/// <summary>Unload-safe external VTank-style loot classifiers.</summary>
|
|
IPluginLootClassifierRegistry LootClassifiers =>
|
|
NoOpPluginLootClassifierRegistry.Instance;
|
|
|
|
/// <summary>
|
|
/// Character reads, spell data and casting. Hosts with no live session
|
|
/// expose <see cref="NoOpAutomationSurface.Instance"/>, so a plugin may
|
|
/// hold one code path and check
|
|
/// <see cref="IAutomationSurface.IsAvailable"/> rather than branching on
|
|
/// host kind.
|
|
/// </summary>
|
|
IAutomationSurface Automation { get; }
|
|
|
|
/// <summary>
|
|
/// Storage rooted at the VTank profile folder (real <c>.usd</c>/
|
|
/// <c>.ast</c>/<c>.af</c> files, VTank's own naming rules) rather than
|
|
/// this plugin's own scoped <see cref="Storage"/> directory — see
|
|
/// <c>AcDream.Plugins.MossTank.VtankProfileDirectory</c>, which
|
|
/// enumerates through this property exclusively (no <c>System.IO</c>,
|
|
/// no per-OS portable-default fallback of its own) so directory
|
|
/// discovery stays entirely host-composed. Defaults to the inert
|
|
/// <see cref="NoOpPluginStorage"/> (<c>IsAvailable</c> false) when the
|
|
/// host has no opinion. A graphical host may root this at a real
|
|
/// installed VTank's own profile directory for direct interop, or at
|
|
/// its own portable per-OS default under
|
|
/// <c>ApplicationPathSet.DataDirectory</c>; that discovery belongs
|
|
/// entirely to the host composing this property, never to the plugin
|
|
/// reading it.
|
|
/// </summary>
|
|
IPluginStorage VtankProfiles => NoOpPluginStorage.Instance;
|
|
}
|