Plugins: share loader provenance helpers
This commit is contained in:
parent
9cbdb075f3
commit
7c1d0785c4
@ -25,56 +25,56 @@ This is an implementation checklist, not a future-design spec.
|
||||
|
||||
## Current Inventory
|
||||
|
||||
| Surface | Current implementation | Target owner | Status | How it has been handled so far |
|
||||
| ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Active runtime registry state | `src/plugins/runtime.ts` plus global plugin runtime state | `src/extension-host/active-registry.ts` | `moved` | Host-owned active registry exists; `src/plugins/runtime.ts` is now a compatibility facade. |
|
||||
| Normalized extension descriptor model | plugin manifests and package metadata interpreted ad hoc across `src/plugins/manifest.ts`, `src/plugins/discovery.ts`, `src/plugins/install.ts` | `src/extension-host/schema.ts` | `partial` | `ResolvedExtension`, `ResolvedContribution`, and `ContributionPolicy` exist; current manifests project into them through compatibility adapters. |
|
||||
| Resolved static registry | flat rows in `src/plugins/manifest-registry.ts` | `src/extension-host/resolved-registry.ts` | `partial` | Manifest records now carry `resolvedExtension`; a host-owned resolved registry view exists for static consumers. |
|
||||
| Manifest/package metadata loading | `src/plugins/manifest.ts`, `src/plugins/discovery.ts`, `src/plugins/install.ts` | `src/extension-host/schema.ts` and `src/extension-host/manifest-registry.ts` | `partial` | Package metadata parsing is routed through host schema helpers; legacy loader flow still supplies the source manifests. |
|
||||
| Loader SDK alias compatibility | `src/plugins/loader.ts` | `src/extension-host/loader-compat.ts` | `partial` | Plugin-SDK alias candidate ordering, alias-file resolution, and scoped alias-map construction now live in host-owned loader compatibility helpers. |
|
||||
| Loader cache key and registry cache control | `src/plugins/loader.ts` | `src/extension-host/loader-cache.ts` | `partial` | Cache-key construction, LRU registry cache reads and writes, and cache clearing now delegate through host-owned loader-cache helpers while preserving the current cache shape and cap. |
|
||||
| Loader provenance and duplicate-order policy | `src/plugins/loader.ts` | `src/extension-host/loader-policy.ts` | `partial` | Plugin-record creation, duplicate precedence, and provenance indexing now live in host-owned loader-policy helpers. |
|
||||
| Loader discovery policy results | mixed inside `src/plugins/loader.ts`, `src/extension-host/loader-policy.ts`, and `src/extension-host/loader-orchestrator.ts` | `src/extension-host/loader-discovery-policy.ts` | `partial` | Open-allowlist discovery warnings now resolve through explicit host-owned discovery-policy results before the orchestrator logs them. |
|
||||
| Loader initial candidate planning and record creation | `src/plugins/loader.ts` | `src/extension-host/loader-records.ts` | `partial` | Duplicate detection, initial record creation, manifest metadata attachment, and first-pass enable-state planning now delegate through host-owned loader-records helpers. |
|
||||
| Loader entry-path opening and module import | `src/plugins/loader.ts` | `src/extension-host/loader-import.ts` | `partial` | Boundary-checked entry opening and module import now delegate through host-owned loader-import helpers while preserving the current trusted in-process loading model. |
|
||||
| Loader module-export, config-validation, and memory-slot decisions | `src/plugins/loader.ts` | `src/extension-host/loader-runtime.ts` | `partial` | Module export resolution, export-metadata application, config validation, and early or final memory-slot decisions now delegate through host-owned loader-runtime helpers. |
|
||||
| Loader post-import planning and register execution | `src/plugins/loader.ts` | `src/extension-host/loader-register.ts` | `partial` | Definition application, post-import validation planning, and `register(...)` execution now delegate through host-owned loader-register helpers while preserving current plugin behavior. |
|
||||
| Loader per-candidate orchestration | `src/plugins/loader.ts` | `src/extension-host/loader-flow.ts` | `partial` | The per-candidate load flow now runs through a host-owned orchestrator that composes planning, import, runtime validation, register execution, and record-state helpers. |
|
||||
| Loader top-level load orchestration | `src/plugins/loader.ts` | `src/extension-host/loader-orchestrator.ts` | `partial` | Cache hits, runtime creation, discovery, manifest loading, candidate ordering, candidate processing, and finalization now route through a host-owned loader orchestrator while `src/plugins/loader.ts` remains the compatibility facade. |
|
||||
| Loader mutable activation state session | local variables in `src/plugins/loader.ts` and `src/extension-host/loader-orchestrator.ts` | `src/extension-host/loader-session.ts` | `partial` | Seen-id tracking, memory-slot selection state, and finalization inputs now live in a host-owned loader session instead of being spread across top-level loader variables. |
|
||||
| Loader activation policy outcomes | open-coded in `src/plugins/loader.ts` and `src/extension-host/loader-flow.ts` | `src/extension-host/loader-activation-policy.ts` | `partial` | Duplicate precedence, config enablement, and early memory-slot gating now resolve through explicit host-owned activation-policy outcomes instead of remaining as inline loader decisions. |
|
||||
| Loader record-state transitions | `src/plugins/loader.ts` | `src/extension-host/loader-state.ts` | `partial` | The loader now enforces an explicit lifecycle transition model (`prepared -> imported -> validated -> registered -> ready`, plus terminal `disabled` and `error`) while still mapping back to compatibility `PluginRecord.status` values. |
|
||||
| Loader finalization policy results | mixed inside `src/plugins/loader.ts`, `src/extension-host/loader-policy.ts`, and `src/extension-host/loader-finalize.ts` | `src/extension-host/loader-finalization-policy.ts` | `partial` | Memory-slot finalization warnings and provenance-based untracked-extension warnings now resolve through explicit host-owned finalization-policy results before the finalizer applies them. |
|
||||
| Loader final cache, warning, and activation finalization | `src/plugins/loader.ts` | `src/extension-host/loader-finalize.ts` | `partial` | Cache writes, untracked-extension warnings, final memory-slot warnings, readiness promotion, and registry activation now delegate through a host-owned loader-finalize helper; broader host lifecycle and policy semantics are still pending. |
|
||||
| Channel lookup | `src/channels/plugins/index.ts`, `src/channels/plugins/registry-loader.ts`, `src/channels/registry.ts` | extension-host-backed registries plus kernel channel contracts | `partial` | Readers now consume the host-owned active registry, but writes still originate from plugin registration. |
|
||||
| Dock lookup | `src/channels/dock.ts` | host-owned static descriptors | `partial` | Runtime lookup now uses the host boundary; dock ownership itself has not moved yet. |
|
||||
| Message-channel normalization | `src/utils/message-channel.ts` | host-owned channel registry view | `partial` | Lookup path now reads through the host-owned active registry. |
|
||||
| Default plugin HTTP route lookup | `src/plugins/http-registry.ts` | host-owned route registry | `partial` | Default registry resolution now uses the host boundary; route registration compatibility still flows through the legacy plugin API. |
|
||||
| Channel catalog static metadata | `src/channels/plugins/catalog.ts` | host-owned static descriptors | `partial` | Package metadata parsing now flows through host schema helpers; full canonical catalog migration has not started. |
|
||||
| Plugin skill discovery | `src/agents/skills/plugin-skills.ts` | host-owned resolved registry | `moved` | Static consumer now reads only resolved-extension data for skill paths and enablement filtering. |
|
||||
| Plugin auto-enable | `src/config/plugin-auto-enable.ts` | host-owned resolved registry | `partial` | Primary logic runs on resolved-extension data; old manifest-registry injection remains as a compatibility input for older callers and tests. |
|
||||
| Config validation indexing | `src/config/validation.ts`, `src/config/resolved-extension-validation.ts` | host-owned resolved registry | `moved` | Validation indexing now builds from resolved-extension records instead of flat manifest rows. |
|
||||
| Config doc baseline generation | `src/config/doc-baseline.ts` | host-owned resolved registry | `moved` | Bundled plugin and channel metadata now load through the resolved-extension registry. |
|
||||
| Plugin loader activation | `src/plugins/loader.ts` | extension host lifecycle + compatibility loader | `partial` | Activation now routes through `src/extension-host/activation.ts`, but discovery, enablement, provenance, module loading, and policy still live in the legacy plugin loader. |
|
||||
| Channel registration writes | `src/plugins/registry.ts` | host-owned channel registry | `partial` | Validation and normalization now delegate to `src/extension-host/runtime-registrations.ts`, but the legacy plugin API still performs the write. |
|
||||
| Provider registration writes | `src/plugins/registry.ts` | host-owned provider registry | `partial` | Provider normalization still happens in plugin-era validation, but duplicate detection and normalized registration shape now delegate to `src/extension-host/runtime-registrations.ts`. |
|
||||
| HTTP route registration writes | `src/plugins/registry.ts` | host-owned route registry | `partial` | Route validation and normalization now delegate to `src/extension-host/runtime-registrations.ts`, but the legacy plugin API still performs the write. |
|
||||
| Gateway method registration writes | `src/plugins/registry.ts` | host-owned runtime contribution registry | `partial` | Duplicate detection and normalized method registration now delegate to `src/extension-host/runtime-registrations.ts`, but the legacy plugin API still performs the write. |
|
||||
| Tool registration writes | `src/plugins/registry.ts` | host-owned tool registry | `partial` | Tool-name normalization and tool-factory shaping now delegate to `src/extension-host/runtime-registrations.ts`, but duplicate handling still follows the legacy tool path. |
|
||||
| CLI registration writes | `src/plugins/registry.ts` | host-owned CLI registry | `partial` | CLI command-name normalization now delegates to `src/extension-host/runtime-registrations.ts`, but the legacy plugin API still performs the write. |
|
||||
| Service registration writes | `src/plugins/registry.ts` | host-owned service registry | `partial` | Service-id normalization now delegates to `src/extension-host/runtime-registrations.ts`, but lifecycle remains legacy-owned. |
|
||||
| Command registration writes | `src/plugins/registry.ts` | host-owned command registry | `partial` | Command-name normalization now delegates to `src/extension-host/runtime-registrations.ts`, but duplicate enforcement still depends on the legacy plugin command registry. |
|
||||
| Context-engine registration writes | `src/plugins/registry.ts` | host-owned context-engine registry | `partial` | Context-engine id normalization now delegates to `src/extension-host/runtime-registrations.ts`, but the actual context-engine registry remains legacy-owned. |
|
||||
| Legacy hook registration writes | `src/plugins/registry.ts` | host-owned hook registry | `partial` | Hook-entry construction and event normalization now delegate to `src/extension-host/runtime-registrations.ts`, but internal-hook bridging still remains in the legacy plugin registry. |
|
||||
| Typed-hook registration writes | `src/plugins/registry.ts` | host-owned typed-hook registry | `partial` | Typed-hook record construction and hook-name validation now delegate to `src/extension-host/runtime-registrations.ts`, but prompt-injection policy and execution semantics remain legacy-owned. |
|
||||
| Hook execution and global runner | `src/plugins/hook-runner-global.ts`, `src/hooks/internal-hooks.ts`, plugin hook registration in `src/plugins/registry.ts` | canonical kernel event stages + host bridges | `not started` | No canonical event-stage migration has landed yet. |
|
||||
| Service lifecycle | `src/plugins/services.ts` and plugin service registration | extension host lifecycle | `not started` | Service startup and teardown still depend on legacy plugin registry/service ownership. |
|
||||
| CLI registration | plugin CLI registration in `src/plugins/registry.ts` and CLI loaders | extension host registry + static descriptors where possible | `not started` | No host-owned CLI registry exists yet. |
|
||||
| Gateway/server methods | `src/plugins/registry.ts` gateway handler registration | host-owned runtime contribution registry | `not started` | Still registered directly into the legacy plugin registry. |
|
||||
| Slot arbitration | `src/plugins/slots.ts` | host-owned arbitration model | `not started` | Current slot selection remains plugin-era logic. |
|
||||
| ACP backend registry | `src/acp/runtime/registry.ts` | host-owned runtime-backend registry | `not started` | ACP backends still mutate a global ACP runtime registry directly. |
|
||||
| Onboarding/install/setup surfaces | `src/plugins/install.ts`, package manifests, channel catalog, onboarding commands | host-owned static descriptors | `partial` | Static metadata normalization has started; full setup/install descriptor migration is not done. |
|
||||
| Pilot migrations | `extensions/thread-ownership`, `extensions/telegram`, `extensions/acpx` | extension-host path with parity tracking | `not started` | No pilot runs through the host path yet. |
|
||||
| Surface | Current implementation | Target owner | Status | How it has been handled so far |
|
||||
| ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Active runtime registry state | `src/plugins/runtime.ts` plus global plugin runtime state | `src/extension-host/active-registry.ts` | `moved` | Host-owned active registry exists; `src/plugins/runtime.ts` is now a compatibility facade. |
|
||||
| Normalized extension descriptor model | plugin manifests and package metadata interpreted ad hoc across `src/plugins/manifest.ts`, `src/plugins/discovery.ts`, `src/plugins/install.ts` | `src/extension-host/schema.ts` | `partial` | `ResolvedExtension`, `ResolvedContribution`, and `ContributionPolicy` exist; current manifests project into them through compatibility adapters. |
|
||||
| Resolved static registry | flat rows in `src/plugins/manifest-registry.ts` | `src/extension-host/resolved-registry.ts` | `partial` | Manifest records now carry `resolvedExtension`; a host-owned resolved registry view exists for static consumers. |
|
||||
| Manifest/package metadata loading | `src/plugins/manifest.ts`, `src/plugins/discovery.ts`, `src/plugins/install.ts` | `src/extension-host/schema.ts` and `src/extension-host/manifest-registry.ts` | `partial` | Package metadata parsing is routed through host schema helpers; legacy loader flow still supplies the source manifests. |
|
||||
| Loader SDK alias compatibility | `src/plugins/loader.ts` | `src/extension-host/loader-compat.ts` | `partial` | Plugin-SDK alias candidate ordering, alias-file resolution, and scoped alias-map construction now live in host-owned loader compatibility helpers. |
|
||||
| Loader cache key and registry cache control | `src/plugins/loader.ts` | `src/extension-host/loader-cache.ts` | `partial` | Cache-key construction, LRU registry cache reads and writes, and cache clearing now delegate through host-owned loader-cache helpers while preserving the current cache shape and cap. |
|
||||
| Loader provenance and duplicate-order policy | `src/plugins/loader.ts` | `src/extension-host/loader-policy.ts` | `partial` | Plugin-record creation, duplicate precedence, and provenance indexing now live in host-owned loader-policy helpers. |
|
||||
| Loader discovery policy results | mixed inside `src/extension-host/loader-policy.ts` and `src/extension-host/loader-orchestrator.ts` | `src/extension-host/loader-discovery-policy.ts` | `partial` | Open-allowlist discovery warnings now resolve through explicit host-owned discovery-policy results before the orchestrator logs them. |
|
||||
| Loader initial candidate planning and record creation | `src/plugins/loader.ts` | `src/extension-host/loader-records.ts` | `partial` | Duplicate detection, initial record creation, manifest metadata attachment, and first-pass enable-state planning now delegate through host-owned loader-records helpers. |
|
||||
| Loader entry-path opening and module import | `src/plugins/loader.ts` | `src/extension-host/loader-import.ts` | `partial` | Boundary-checked entry opening and module import now delegate through host-owned loader-import helpers while preserving the current trusted in-process loading model. |
|
||||
| Loader module-export, config-validation, and memory-slot decisions | `src/plugins/loader.ts` | `src/extension-host/loader-runtime.ts` | `partial` | Module export resolution, export-metadata application, config validation, and early or final memory-slot decisions now delegate through host-owned loader-runtime helpers. |
|
||||
| Loader post-import planning and register execution | `src/plugins/loader.ts` | `src/extension-host/loader-register.ts` | `partial` | Definition application, post-import validation planning, and `register(...)` execution now delegate through host-owned loader-register helpers while preserving current plugin behavior. |
|
||||
| Loader per-candidate orchestration | `src/plugins/loader.ts` | `src/extension-host/loader-flow.ts` | `partial` | The per-candidate load flow now runs through a host-owned orchestrator that composes planning, import, runtime validation, register execution, and record-state helpers. |
|
||||
| Loader top-level load orchestration | `src/plugins/loader.ts` | `src/extension-host/loader-orchestrator.ts` | `partial` | Cache hits, runtime creation, discovery, manifest loading, candidate ordering, candidate processing, and finalization now route through a host-owned loader orchestrator while `src/plugins/loader.ts` remains the compatibility facade. |
|
||||
| Loader mutable activation state session | local variables in `src/extension-host/loader-orchestrator.ts` | `src/extension-host/loader-session.ts` | `partial` | Seen-id tracking, memory-slot selection state, and finalization inputs now live in a host-owned loader session instead of being spread across top-level loader variables. |
|
||||
| Loader activation policy outcomes | open-coded in `src/extension-host/loader-flow.ts` | `src/extension-host/loader-activation-policy.ts` | `partial` | Duplicate precedence, config enablement, and early memory-slot gating now resolve through explicit host-owned activation-policy outcomes instead of remaining as inline loader decisions. |
|
||||
| Loader record-state transitions | `src/plugins/loader.ts` | `src/extension-host/loader-state.ts` | `partial` | The loader now enforces an explicit lifecycle transition model (`prepared -> imported -> validated -> registered -> ready`, plus terminal `disabled` and `error`) while still mapping back to compatibility `PluginRecord.status` values. |
|
||||
| Loader finalization policy results | mixed inside `src/extension-host/loader-policy.ts` and `src/extension-host/loader-finalize.ts` | `src/extension-host/loader-finalization-policy.ts` | `partial` | Memory-slot finalization warnings and provenance-based untracked-extension warnings now resolve through explicit host-owned finalization-policy results before the finalizer applies them. |
|
||||
| Loader final cache, readiness, and activation finalization | `src/plugins/loader.ts` | `src/extension-host/loader-finalize.ts` | `partial` | Cache writes, readiness promotion, and registry activation now delegate through a host-owned loader-finalize helper; broader host lifecycle and policy semantics are still pending. |
|
||||
| Channel lookup | `src/channels/plugins/index.ts`, `src/channels/plugins/registry-loader.ts`, `src/channels/registry.ts` | extension-host-backed registries plus kernel channel contracts | `partial` | Readers now consume the host-owned active registry, but writes still originate from plugin registration. |
|
||||
| Dock lookup | `src/channels/dock.ts` | host-owned static descriptors | `partial` | Runtime lookup now uses the host boundary; dock ownership itself has not moved yet. |
|
||||
| Message-channel normalization | `src/utils/message-channel.ts` | host-owned channel registry view | `partial` | Lookup path now reads through the host-owned active registry. |
|
||||
| Default plugin HTTP route lookup | `src/plugins/http-registry.ts` | host-owned route registry | `partial` | Default registry resolution now uses the host boundary; route registration compatibility still flows through the legacy plugin API. |
|
||||
| Channel catalog static metadata | `src/channels/plugins/catalog.ts` | host-owned static descriptors | `partial` | Package metadata parsing now flows through host schema helpers; full canonical catalog migration has not started. |
|
||||
| Plugin skill discovery | `src/agents/skills/plugin-skills.ts` | host-owned resolved registry | `moved` | Static consumer now reads only resolved-extension data for skill paths and enablement filtering. |
|
||||
| Plugin auto-enable | `src/config/plugin-auto-enable.ts` | host-owned resolved registry | `partial` | Primary logic runs on resolved-extension data; old manifest-registry injection remains as a compatibility input for older callers and tests. |
|
||||
| Config validation indexing | `src/config/validation.ts`, `src/config/resolved-extension-validation.ts` | host-owned resolved registry | `moved` | Validation indexing now builds from resolved-extension records instead of flat manifest rows. |
|
||||
| Config doc baseline generation | `src/config/doc-baseline.ts` | host-owned resolved registry | `moved` | Bundled plugin and channel metadata now load through the resolved-extension registry. |
|
||||
| Plugin loader activation | `src/plugins/loader.ts` | extension host lifecycle + compatibility loader | `partial` | Activation now routes through `src/extension-host/activation.ts`, but discovery, enablement, provenance, module loading, and policy still live in the legacy plugin loader. |
|
||||
| Channel registration writes | `src/plugins/registry.ts` | host-owned channel registry | `partial` | Validation and normalization now delegate to `src/extension-host/runtime-registrations.ts`, but the legacy plugin API still performs the write. |
|
||||
| Provider registration writes | `src/plugins/registry.ts` | host-owned provider registry | `partial` | Provider normalization still happens in plugin-era validation, but duplicate detection and normalized registration shape now delegate to `src/extension-host/runtime-registrations.ts`. |
|
||||
| HTTP route registration writes | `src/plugins/registry.ts` | host-owned route registry | `partial` | Route validation and normalization now delegate to `src/extension-host/runtime-registrations.ts`, but the legacy plugin API still performs the write. |
|
||||
| Gateway method registration writes | `src/plugins/registry.ts` | host-owned runtime contribution registry | `partial` | Duplicate detection and normalized method registration now delegate to `src/extension-host/runtime-registrations.ts`, but the legacy plugin API still performs the write. |
|
||||
| Tool registration writes | `src/plugins/registry.ts` | host-owned tool registry | `partial` | Tool-name normalization and tool-factory shaping now delegate to `src/extension-host/runtime-registrations.ts`, but duplicate handling still follows the legacy tool path. |
|
||||
| CLI registration writes | `src/plugins/registry.ts` | host-owned CLI registry | `partial` | CLI command-name normalization now delegates to `src/extension-host/runtime-registrations.ts`, but the legacy plugin API still performs the write. |
|
||||
| Service registration writes | `src/plugins/registry.ts` | host-owned service registry | `partial` | Service-id normalization now delegates to `src/extension-host/runtime-registrations.ts`, but lifecycle remains legacy-owned. |
|
||||
| Command registration writes | `src/plugins/registry.ts` | host-owned command registry | `partial` | Command-name normalization now delegates to `src/extension-host/runtime-registrations.ts`, but duplicate enforcement still depends on the legacy plugin command registry. |
|
||||
| Context-engine registration writes | `src/plugins/registry.ts` | host-owned context-engine registry | `partial` | Context-engine id normalization now delegates to `src/extension-host/runtime-registrations.ts`, but the actual context-engine registry remains legacy-owned. |
|
||||
| Legacy hook registration writes | `src/plugins/registry.ts` | host-owned hook registry | `partial` | Hook-entry construction and event normalization now delegate to `src/extension-host/runtime-registrations.ts`, but internal-hook bridging still remains in the legacy plugin registry. |
|
||||
| Typed-hook registration writes | `src/plugins/registry.ts` | host-owned typed-hook registry | `partial` | Typed-hook record construction and hook-name validation now delegate to `src/extension-host/runtime-registrations.ts`, but prompt-injection policy and execution semantics remain legacy-owned. |
|
||||
| Hook execution and global runner | `src/plugins/hook-runner-global.ts`, `src/hooks/internal-hooks.ts`, plugin hook registration in `src/plugins/registry.ts` | canonical kernel event stages + host bridges | `not started` | No canonical event-stage migration has landed yet. |
|
||||
| Service lifecycle | `src/plugins/services.ts` and plugin service registration | extension host lifecycle | `not started` | Service startup and teardown still depend on legacy plugin registry/service ownership. |
|
||||
| CLI registration | plugin CLI registration in `src/plugins/registry.ts` and CLI loaders | extension host registry + static descriptors where possible | `not started` | No host-owned CLI registry exists yet. |
|
||||
| Gateway/server methods | `src/plugins/registry.ts` gateway handler registration | host-owned runtime contribution registry | `not started` | Still registered directly into the legacy plugin registry. |
|
||||
| Slot arbitration | `src/plugins/slots.ts` | host-owned arbitration model | `not started` | Current slot selection remains plugin-era logic. |
|
||||
| ACP backend registry | `src/acp/runtime/registry.ts` | host-owned runtime-backend registry | `not started` | ACP backends still mutate a global ACP runtime registry directly. |
|
||||
| Onboarding/install/setup surfaces | `src/plugins/install.ts`, package manifests, channel catalog, onboarding commands | host-owned static descriptors | `partial` | Static metadata normalization has started; full setup/install descriptor migration is not done. |
|
||||
| Pilot migrations | `extensions/thread-ownership`, `extensions/telegram`, `extensions/acpx` | extension-host path with parity tracking | `not started` | No pilot runs through the host path yet. |
|
||||
|
||||
## Completed Pattern So Far
|
||||
|
||||
|
||||
@ -1,49 +1,10 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import type { PluginRegistry } from "../plugins/registry.js";
|
||||
import type { PluginDiagnostic } from "../plugins/types.js";
|
||||
import type { ExtensionHostProvenanceIndex } from "./loader-policy.js";
|
||||
|
||||
function safeRealpathOrResolve(value: string): string {
|
||||
try {
|
||||
return fs.realpathSync(value);
|
||||
} catch {
|
||||
return path.resolve(value);
|
||||
}
|
||||
}
|
||||
|
||||
function matchesPathMatcher(
|
||||
matcher: { exact: Set<string>; dirs: string[] },
|
||||
sourcePath: string,
|
||||
): boolean {
|
||||
if (matcher.exact.has(sourcePath)) {
|
||||
return true;
|
||||
}
|
||||
return matcher.dirs.some(
|
||||
(dirPath) => sourcePath === dirPath || sourcePath.startsWith(`${dirPath}/`),
|
||||
);
|
||||
}
|
||||
|
||||
function isTrackedByProvenance(params: {
|
||||
pluginId: string;
|
||||
source: string;
|
||||
index: ExtensionHostProvenanceIndex;
|
||||
env: NodeJS.ProcessEnv;
|
||||
}): boolean {
|
||||
const sourcePath = params.source.startsWith("~")
|
||||
? `${params.env.HOME ?? ""}${params.source.slice(1)}`
|
||||
: params.source;
|
||||
const installRule = params.index.installRules.get(params.pluginId);
|
||||
if (installRule) {
|
||||
if (installRule.trackedWithoutPaths) {
|
||||
return true;
|
||||
}
|
||||
if (matchesPathMatcher(installRule.matcher, sourcePath)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return matchesPathMatcher(params.index.loadPathMatcher, sourcePath);
|
||||
}
|
||||
import {
|
||||
isExtensionHostTrackedByProvenance,
|
||||
safeRealpathOrResolveExtensionHostPath,
|
||||
type ExtensionHostProvenanceIndex,
|
||||
} from "./loader-provenance.js";
|
||||
|
||||
export function resolveExtensionHostFinalizationPolicy(params: {
|
||||
registry: PluginRegistry;
|
||||
@ -70,7 +31,7 @@ export function resolveExtensionHostFinalizationPolicy(params: {
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
isTrackedByProvenance({
|
||||
isExtensionHostTrackedByProvenance({
|
||||
pluginId: plugin.id,
|
||||
source: plugin.source,
|
||||
index: params.provenance,
|
||||
@ -88,7 +49,7 @@ export function resolveExtensionHostFinalizationPolicy(params: {
|
||||
message,
|
||||
});
|
||||
warningMessages.push(
|
||||
`[plugins] ${plugin.id}: ${message} (${safeRealpathOrResolve(plugin.source)})`,
|
||||
`[plugins] ${plugin.id}: ${message} (${safeRealpathOrResolveExtensionHostPath(plugin.source)})`,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -7,7 +7,6 @@ import {
|
||||
getCachedExtensionHostRegistry,
|
||||
setCachedExtensionHostRegistry,
|
||||
} from "../extension-host/loader-cache.js";
|
||||
import { resolveExtensionHostDiscoveryPolicy } from "../extension-host/loader-discovery-policy.js";
|
||||
import {
|
||||
buildExtensionHostProvenanceIndex,
|
||||
compareExtensionHostDuplicateCandidateOrder,
|
||||
@ -24,6 +23,7 @@ import { createPluginRuntime, type CreatePluginRuntimeOptions } from "../plugins
|
||||
import type { PluginRuntime } from "../plugins/runtime/types.js";
|
||||
import type { OpenClawPluginModule, PluginLogger } from "../plugins/types.js";
|
||||
import { resolvePluginSdkAlias, resolvePluginSdkScopedAliasMap } from "./loader-compat.js";
|
||||
import { resolveExtensionHostDiscoveryPolicy } from "./loader-discovery-policy.js";
|
||||
import {
|
||||
createExtensionHostLoaderSession,
|
||||
finalizeExtensionHostLoaderSession,
|
||||
|
||||
@ -1,29 +1,19 @@
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import type { PluginCandidate } from "../plugins/discovery.js";
|
||||
import { isPathInside, safeStatSync } from "../plugins/path-safety.js";
|
||||
import type { PluginRecord, PluginRegistry } from "../plugins/registry.js";
|
||||
import type { PluginDiagnostic, PluginLogger } from "../plugins/types.js";
|
||||
import { resolveUserPath } from "../utils.js";
|
||||
import {
|
||||
addExtensionHostPathToMatcher,
|
||||
createExtensionHostPathMatcher,
|
||||
matchesExplicitExtensionHostInstallRule,
|
||||
type ExtensionHostInstallTrackingRule,
|
||||
type ExtensionHostProvenanceIndex,
|
||||
} from "./loader-provenance.js";
|
||||
import {
|
||||
appendExtensionHostPluginRecord,
|
||||
setExtensionHostPluginRecordLifecycleState,
|
||||
} from "./loader-state.js";
|
||||
|
||||
type PathMatcher = {
|
||||
exact: Set<string>;
|
||||
dirs: string[];
|
||||
};
|
||||
|
||||
type InstallTrackingRule = {
|
||||
trackedWithoutPaths: boolean;
|
||||
matcher: PathMatcher;
|
||||
};
|
||||
|
||||
export type ExtensionHostProvenanceIndex = {
|
||||
loadPathMatcher: PathMatcher;
|
||||
installRules: Map<string, InstallTrackingRule>;
|
||||
};
|
||||
|
||||
export function createExtensionHostPluginRecord(params: {
|
||||
id: string;
|
||||
name?: string;
|
||||
@ -106,57 +96,22 @@ export function pushExtensionHostDiagnostics(
|
||||
diagnostics.push(...append);
|
||||
}
|
||||
|
||||
function createPathMatcher(): PathMatcher {
|
||||
return { exact: new Set<string>(), dirs: [] };
|
||||
}
|
||||
|
||||
function addPathToMatcher(
|
||||
matcher: PathMatcher,
|
||||
rawPath: string,
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
): void {
|
||||
const trimmed = rawPath.trim();
|
||||
if (!trimmed) {
|
||||
return;
|
||||
}
|
||||
const resolved = resolveUserPath(trimmed, env);
|
||||
if (!resolved) {
|
||||
return;
|
||||
}
|
||||
if (matcher.exact.has(resolved) || matcher.dirs.includes(resolved)) {
|
||||
return;
|
||||
}
|
||||
const stat = safeStatSync(resolved);
|
||||
if (stat?.isDirectory()) {
|
||||
matcher.dirs.push(resolved);
|
||||
return;
|
||||
}
|
||||
matcher.exact.add(resolved);
|
||||
}
|
||||
|
||||
function matchesPathMatcher(matcher: PathMatcher, sourcePath: string): boolean {
|
||||
if (matcher.exact.has(sourcePath)) {
|
||||
return true;
|
||||
}
|
||||
return matcher.dirs.some((dirPath) => isPathInside(dirPath, sourcePath));
|
||||
}
|
||||
|
||||
export function buildExtensionHostProvenanceIndex(params: {
|
||||
config: OpenClawConfig;
|
||||
normalizedLoadPaths: string[];
|
||||
env: NodeJS.ProcessEnv;
|
||||
}): ExtensionHostProvenanceIndex {
|
||||
const loadPathMatcher = createPathMatcher();
|
||||
const loadPathMatcher = createExtensionHostPathMatcher();
|
||||
for (const loadPath of params.normalizedLoadPaths) {
|
||||
addPathToMatcher(loadPathMatcher, loadPath, params.env);
|
||||
addExtensionHostPathToMatcher(loadPathMatcher, loadPath, params.env);
|
||||
}
|
||||
|
||||
const installRules = new Map<string, InstallTrackingRule>();
|
||||
const installRules = new Map<string, ExtensionHostInstallTrackingRule>();
|
||||
const installs = params.config.plugins?.installs ?? {};
|
||||
for (const [pluginId, install] of Object.entries(installs)) {
|
||||
const rule: InstallTrackingRule = {
|
||||
const rule: ExtensionHostInstallTrackingRule = {
|
||||
trackedWithoutPaths: false,
|
||||
matcher: createPathMatcher(),
|
||||
matcher: createExtensionHostPathMatcher(),
|
||||
};
|
||||
const trackedPaths = [install.installPath, install.sourcePath]
|
||||
.map((entry) => (typeof entry === "string" ? entry.trim() : ""))
|
||||
@ -165,7 +120,7 @@ export function buildExtensionHostProvenanceIndex(params: {
|
||||
rule.trackedWithoutPaths = true;
|
||||
} else {
|
||||
for (const trackedPath of trackedPaths) {
|
||||
addPathToMatcher(rule.matcher, trackedPath, params.env);
|
||||
addExtensionHostPathToMatcher(rule.matcher, trackedPath, params.env);
|
||||
}
|
||||
}
|
||||
installRules.set(pluginId, rule);
|
||||
@ -174,20 +129,6 @@ export function buildExtensionHostProvenanceIndex(params: {
|
||||
return { loadPathMatcher, installRules };
|
||||
}
|
||||
|
||||
function matchesExplicitInstallRule(params: {
|
||||
pluginId: string;
|
||||
source: string;
|
||||
index: ExtensionHostProvenanceIndex;
|
||||
env: NodeJS.ProcessEnv;
|
||||
}): boolean {
|
||||
const sourcePath = resolveUserPath(params.source, params.env);
|
||||
const installRule = params.index.installRules.get(params.pluginId);
|
||||
if (!installRule || installRule.trackedWithoutPaths) {
|
||||
return false;
|
||||
}
|
||||
return matchesPathMatcher(installRule.matcher, sourcePath);
|
||||
}
|
||||
|
||||
function resolveCandidateDuplicateRank(params: {
|
||||
candidate: PluginCandidate;
|
||||
manifestByRoot: Map<string, { id: string }>;
|
||||
@ -199,7 +140,7 @@ function resolveCandidateDuplicateRank(params: {
|
||||
const isExplicitInstall =
|
||||
params.candidate.origin === "global" &&
|
||||
pluginId !== undefined &&
|
||||
matchesExplicitInstallRule({
|
||||
matchesExplicitExtensionHostInstallRule({
|
||||
pluginId,
|
||||
source: params.candidate.source,
|
||||
index: params.provenance,
|
||||
|
||||
88
src/extension-host/loader-provenance.test.ts
Normal file
88
src/extension-host/loader-provenance.test.ts
Normal file
@ -0,0 +1,88 @@
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import {
|
||||
addExtensionHostPathToMatcher,
|
||||
createExtensionHostPathMatcher,
|
||||
isExtensionHostTrackedByProvenance,
|
||||
matchesExplicitExtensionHostInstallRule,
|
||||
safeRealpathOrResolveExtensionHostPath,
|
||||
type ExtensionHostProvenanceIndex,
|
||||
} from "./loader-provenance.js";
|
||||
|
||||
const tempDirs: string[] = [];
|
||||
|
||||
function makeTempDir(): string {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-loader-provenance-"));
|
||||
tempDirs.push(dir);
|
||||
return dir;
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
for (const dir of tempDirs.splice(0)) {
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
describe("extension host loader provenance", () => {
|
||||
it("tracks plugins by load path directories", () => {
|
||||
const trackedDir = makeTempDir();
|
||||
const trackedFile = path.join(trackedDir, "tracked.js");
|
||||
fs.writeFileSync(trackedFile, "export {};\n", "utf8");
|
||||
|
||||
const loadPathMatcher = createExtensionHostPathMatcher();
|
||||
addExtensionHostPathToMatcher(loadPathMatcher, trackedDir);
|
||||
|
||||
const index: ExtensionHostProvenanceIndex = {
|
||||
loadPathMatcher,
|
||||
installRules: new Map(),
|
||||
};
|
||||
|
||||
expect(
|
||||
isExtensionHostTrackedByProvenance({
|
||||
pluginId: "tracked",
|
||||
source: trackedFile,
|
||||
index,
|
||||
env: process.env,
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("matches explicit install rules only when tracked paths are present", () => {
|
||||
const installDir = makeTempDir();
|
||||
const installFile = path.join(installDir, "plugin.js");
|
||||
fs.writeFileSync(installFile, "export {};\n", "utf8");
|
||||
|
||||
const installMatcher = createExtensionHostPathMatcher();
|
||||
addExtensionHostPathToMatcher(installMatcher, installDir);
|
||||
|
||||
const index: ExtensionHostProvenanceIndex = {
|
||||
loadPathMatcher: createExtensionHostPathMatcher(),
|
||||
installRules: new Map([
|
||||
[
|
||||
"demo",
|
||||
{
|
||||
trackedWithoutPaths: false,
|
||||
matcher: installMatcher,
|
||||
},
|
||||
],
|
||||
]),
|
||||
};
|
||||
|
||||
expect(
|
||||
matchesExplicitExtensionHostInstallRule({
|
||||
pluginId: "demo",
|
||||
source: installFile,
|
||||
index,
|
||||
env: process.env,
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("falls back to resolved paths when realpath fails", () => {
|
||||
const missingPath = path.join(makeTempDir(), "missing.js");
|
||||
|
||||
expect(safeRealpathOrResolveExtensionHostPath(missingPath)).toBe(path.resolve(missingPath));
|
||||
});
|
||||
});
|
||||
98
src/extension-host/loader-provenance.ts
Normal file
98
src/extension-host/loader-provenance.ts
Normal file
@ -0,0 +1,98 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { isPathInside, safeStatSync } from "../plugins/path-safety.js";
|
||||
import { resolveUserPath } from "../utils.js";
|
||||
|
||||
export type ExtensionHostPathMatcher = {
|
||||
exact: Set<string>;
|
||||
dirs: string[];
|
||||
};
|
||||
|
||||
export type ExtensionHostInstallTrackingRule = {
|
||||
trackedWithoutPaths: boolean;
|
||||
matcher: ExtensionHostPathMatcher;
|
||||
};
|
||||
|
||||
export type ExtensionHostProvenanceIndex = {
|
||||
loadPathMatcher: ExtensionHostPathMatcher;
|
||||
installRules: Map<string, ExtensionHostInstallTrackingRule>;
|
||||
};
|
||||
|
||||
export function safeRealpathOrResolveExtensionHostPath(value: string): string {
|
||||
try {
|
||||
return fs.realpathSync(value);
|
||||
} catch {
|
||||
return path.resolve(value);
|
||||
}
|
||||
}
|
||||
|
||||
export function createExtensionHostPathMatcher(): ExtensionHostPathMatcher {
|
||||
return { exact: new Set<string>(), dirs: [] };
|
||||
}
|
||||
|
||||
export function addExtensionHostPathToMatcher(
|
||||
matcher: ExtensionHostPathMatcher,
|
||||
rawPath: string,
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
): void {
|
||||
const trimmed = rawPath.trim();
|
||||
if (!trimmed) {
|
||||
return;
|
||||
}
|
||||
const resolved = resolveUserPath(trimmed, env);
|
||||
if (!resolved) {
|
||||
return;
|
||||
}
|
||||
if (matcher.exact.has(resolved) || matcher.dirs.includes(resolved)) {
|
||||
return;
|
||||
}
|
||||
const stat = safeStatSync(resolved);
|
||||
if (stat?.isDirectory()) {
|
||||
matcher.dirs.push(resolved);
|
||||
return;
|
||||
}
|
||||
matcher.exact.add(resolved);
|
||||
}
|
||||
|
||||
export function matchesExtensionHostPathMatcher(
|
||||
matcher: ExtensionHostPathMatcher,
|
||||
sourcePath: string,
|
||||
): boolean {
|
||||
if (matcher.exact.has(sourcePath)) {
|
||||
return true;
|
||||
}
|
||||
return matcher.dirs.some((dirPath) => isPathInside(dirPath, sourcePath));
|
||||
}
|
||||
|
||||
export function isExtensionHostTrackedByProvenance(params: {
|
||||
pluginId: string;
|
||||
source: string;
|
||||
index: ExtensionHostProvenanceIndex;
|
||||
env: NodeJS.ProcessEnv;
|
||||
}): boolean {
|
||||
const sourcePath = resolveUserPath(params.source, params.env);
|
||||
const installRule = params.index.installRules.get(params.pluginId);
|
||||
if (installRule) {
|
||||
if (installRule.trackedWithoutPaths) {
|
||||
return true;
|
||||
}
|
||||
if (matchesExtensionHostPathMatcher(installRule.matcher, sourcePath)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return matchesExtensionHostPathMatcher(params.index.loadPathMatcher, sourcePath);
|
||||
}
|
||||
|
||||
export function matchesExplicitExtensionHostInstallRule(params: {
|
||||
pluginId: string;
|
||||
source: string;
|
||||
index: ExtensionHostProvenanceIndex;
|
||||
env: NodeJS.ProcessEnv;
|
||||
}): boolean {
|
||||
const sourcePath = resolveUserPath(params.source, params.env);
|
||||
const installRule = params.index.installRules.get(params.pluginId);
|
||||
if (!installRule || installRule.trackedWithoutPaths) {
|
||||
return false;
|
||||
}
|
||||
return matchesExtensionHostPathMatcher(installRule.matcher, sourcePath);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user