feat: integrate marketplace plugins with coding runtime

This commit is contained in:
2026-08-28 18:02:51 +08:00
parent 1d64b89499
commit 05917a789a
21 changed files with 1921 additions and 29 deletions

View File

@@ -0,0 +1,478 @@
import path from 'node:path';
import type {
CodingPluginDefinition,
CodingPluginToolDefinition,
} from '../../shared/coding-plugins';
import {
CORE_CODING_SKILL_IDS,
type CodingSkillId,
} from '../../shared/coding-skills';
import {
accountBindingKey,
type AccountBinding,
type MarketplaceLibrarySnapshot,
} from './account-plugin-cache';
import type { InstalledRelease } from './package-store';
import type {
PluginBillingPolicy,
PluginCatalog,
PluginCatalogOperation,
PluginPolicyClientState,
} from '../services/plugin-policy-client';
/** The serialisable Skill resource consumed by every next Pi worker. */
export interface SkillEntry {
readonly id: CodingSkillId;
readonly entryPath: string;
}
/** A policy row copied from the last verified server catalog. */
export interface RuntimePolicy {
readonly pluginId: string;
readonly contractVersion: number;
readonly capabilityId: string;
readonly operation: string;
readonly billing: PluginBillingPolicy;
}
export type PluginUnavailableReasonCode =
| 'account_required'
| 'library_required'
| 'library_unavailable'
| 'release_not_installed'
| 'release_invalid'
| 'project_disabled'
| 'skill_unassigned'
| 'runtime_suspended'
| 'policy_unavailable'
| 'policy_unsupported'
| 'billing_unavailable';
export interface PluginUnavailableReason {
readonly pluginId: string;
readonly code: PluginUnavailableReasonCode;
readonly message: string;
}
/**
* Main-owned, frozen worker contract. No renderer or worker state is an
* authority: all consumers receive this exact projection for one worker.
*/
export interface EffectivePluginSnapshot {
readonly accountSessionId: string;
readonly projectId: string;
readonly pluginReleaseIds: readonly string[];
readonly effectiveSkillIds: readonly CodingSkillId[];
readonly skillEntries: readonly SkillEntry[];
readonly toolDefinitions: readonly CodingPluginToolDefinition[];
readonly runtimePolicies: readonly RuntimePolicy[];
readonly unavailableReasons: readonly PluginUnavailableReason[];
}
export interface EffectivePluginResolverInput {
readonly projectId: string;
readonly projectPath: string;
readonly assignedSkillIds: readonly string[];
readonly role: 'parent' | 'child';
/** Test and composition seam for an already fetched Account Library. */
readonly library?: MarketplaceLibrarySnapshot | null;
}
export interface EffectivePluginResolverOptions {
readonly definitions?: readonly CodingPluginDefinition[];
readonly getDefinitions?: () => readonly CodingPluginDefinition[] | Promise<readonly CodingPluginDefinition[]>;
readonly installedDefinitions?: readonly CodingPluginDefinition[];
readonly getInstalled?: (pluginId: string) => Promise<InstalledRelease | null> | InstalledRelease | null;
readonly packageStore?: {
readInstalledIndex(): Promise<readonly { pluginId: string }[]>;
getInstalled(pluginId: string): Promise<InstalledRelease | null>;
};
readonly getLibrary?: (binding: AccountBinding) => Promise<MarketplaceLibrarySnapshot | null> | MarketplaceLibrarySnapshot | null;
readonly marketplace?: {
readLibrary(): Promise<MarketplaceLibrarySnapshot>;
};
readonly accountCache?: {
getLibrary(binding: AccountBinding): MarketplaceLibrarySnapshot | null;
};
readonly getAccountBinding?: () => AccountBinding | null;
readonly getAccountSessionId?: () => string;
readonly getEnabledPluginIds?: (projectPath: string) => Promise<readonly string[]>;
readonly projectPlugins?: {
getEnabledPluginIds(projectPath: string): Promise<readonly string[]>;
};
readonly policyClient?: {
getState(): PluginPolicyClientState;
refresh(): Promise<unknown>;
};
}
export interface EffectivePluginSkillSource {
readonly id: string;
readonly pluginId: string;
readonly packageRoot: string;
readonly directory: string;
readonly entryPath: string;
}
interface DefinitionRecord {
readonly definition: CodingPluginDefinition;
readonly installed: boolean;
}
const EMPTY_POLICY_STATE: PluginPolicyClientState = {
status: 'unavailable',
catalog: null,
revision: 0,
lastVerifiedAt: null,
};
function clone<T>(value: T): T {
return structuredClone(value);
}
function freezeArray<T>(value: readonly T[]): readonly T[] {
return Object.freeze([...value]);
}
function deepFreeze<T>(value: T): T {
if (value && typeof value === 'object' && !Object.isFrozen(value)) {
for (const child of Object.values(value as Record<string, unknown>)) deepFreeze(child);
Object.freeze(value);
}
return value;
}
function freezeSnapshot(value: EffectivePluginSnapshot): EffectivePluginSnapshot {
const effectiveSkillIds = [...new Set(value.effectiveSkillIds)];
const skillEntries = [...new Map(value.skillEntries.map((entry) => [entry.id, entry])).values()];
return Object.freeze({
accountSessionId: value.accountSessionId,
projectId: value.projectId,
pluginReleaseIds: freezeArray(value.pluginReleaseIds),
effectiveSkillIds: freezeArray(effectiveSkillIds),
skillEntries: freezeArray(skillEntries.map((entry) => Object.freeze({ ...entry }))),
toolDefinitions: freezeArray(value.toolDefinitions.map((tool) => deepFreeze(clone(tool)))),
runtimePolicies: freezeArray(value.runtimePolicies.map((policy) => deepFreeze({
...policy,
billing: clone(policy.billing),
}))),
unavailableReasons: freezeArray(value.unavailableReasons.map((reason) => Object.freeze({ ...reason }))),
});
}
function accountSessionId(
options: EffectivePluginResolverOptions,
binding: AccountBinding | null,
): string {
const provided = options.getAccountSessionId?.();
if (provided && provided.trim()) return provided;
return binding ? accountBindingKey(binding) : 'anonymous';
}
function normalizeIds(value: readonly string[]): string[] {
return [...new Set(value.map((id) => id.trim()).filter(Boolean))];
}
function policyOperation(
catalog: PluginCatalog | null,
definition: CodingPluginDefinition,
capabilityId: string,
operation: string,
): PluginCatalogOperation | null {
const plugin = catalog?.plugins.find(({ plugin_id }) => plugin_id === definition.id);
if (!plugin || plugin.status !== 'active'
|| !plugin.supported_contract_versions.includes(definition.contractVersion)) return null;
return plugin.capabilities
.find(({ capability_id }) => capability_id === capabilityId)
?.operations.find((candidate) => candidate.operation === operation) ?? null;
}
function policyIsBillingAvailable(policy: PluginCatalogOperation): boolean {
return policy.billing.mode !== 'platform_metered'
|| !('status' in policy.billing) || policy.billing.status !== 'billing_unavailable';
}
function unavailable(
pluginId: string,
code: PluginUnavailableReasonCode,
message: string,
): PluginUnavailableReason {
return { pluginId, code, message };
}
function validateBinding(value: AccountBinding | null): AccountBinding | null {
if (!value || typeof value.accountKey !== 'string' || value.accountKey.length === 0
|| value.accountKey.length > 512 || !Number.isSafeInteger(value.epoch) || value.epoch < 0) {
return null;
}
return { accountKey: value.accountKey, epoch: value.epoch };
}
/**
* Resolve a worker snapshot from separated Marketplace, Package Store,
* project, assignment, and policy state. This module deliberately performs
* no package execution and never trusts paths supplied by a package.
*/
export class EffectivePluginResolver {
private readonly cachedLibraries = new Map<string, MarketplaceLibrarySnapshot>();
constructor(private readonly options: EffectivePluginResolverOptions) {}
async resolve(input: EffectivePluginResolverInput): Promise<EffectivePluginSnapshot> {
const definitions = await this.definitionRecords();
const assigned = normalizeIds(input.assignedSkillIds);
const coreIds = new Set<string>(CORE_CODING_SKILL_IDS);
const effectiveSkillIds: CodingSkillId[] = assigned.filter((id) => coreIds.has(id));
const skillEntries: SkillEntry[] = effectiveSkillIds.map((id) => ({
id,
entryPath: `${id}/SKILL.md`,
}));
const pluginReleaseIds: string[] = [];
const toolDefinitions: CodingPluginToolDefinition[] = [];
const runtimePolicies: RuntimePolicy[] = [];
const unavailableReasons: PluginUnavailableReason[] = [];
// Child workers intentionally receive only core resources. Still validate
// assignment IDs above so malformed project configuration remains visible.
if (input.role === 'child') {
return freezeSnapshot({
accountSessionId: accountSessionId(this.options, validateBinding(this.options.getAccountBinding?.() ?? null)),
projectId: input.projectId,
pluginReleaseIds,
effectiveSkillIds,
skillEntries,
toolDefinitions,
runtimePolicies,
unavailableReasons,
});
}
const binding = validateBinding(this.options.getAccountBinding?.() ?? null);
const userDefinitions = definitions.filter(({ definition }) => definition.acquisitionMode === 'user_acquired');
const enabled = new Set(await this.enabledPluginIds(input.projectPath));
const requiresLibrary = userDefinitions.some(({ definition, installed }) => (
installed && enabled.has(definition.id)
&& definition.skills.some(({ id }) => assigned.includes(id))
));
const library = input.library !== undefined
? input.library
: await this.libraryFor(binding, requiresLibrary);
const libraryById = new Map((library?.items ?? []).map((entry) => [entry.pluginId, entry]));
let policyState: PluginPolicyClientState = this.options.policyClient?.getState() ?? EMPTY_POLICY_STATE;
const serverDefinitions = definitions.filter(({ definition }) => definition.requiresBackend);
const needsPolicyRefresh = policyState.status !== 'current'
&& serverDefinitions.some(({ definition, installed }) => (
installed && enabled.has(definition.id) && definition.skills.some(({ id }) => assigned.includes(id))
));
if (needsPolicyRefresh && this.options.policyClient) {
await this.options.policyClient.refresh();
policyState = this.options.policyClient.getState();
}
for (const { definition, installed } of definitions) {
const selectedSkills = definition.skills.filter(({ id }) => assigned.includes(id));
if (selectedSkills.length === 0) {
unavailableReasons.push(unavailable(definition.id, 'skill_unassigned', 'Plugin Skill is not assigned'));
continue;
}
if (!installed) {
unavailableReasons.push(unavailable(definition.id, 'release_not_installed', 'Plugin Release is not installed'));
continue;
}
if (!enabled.has(definition.id)) {
unavailableReasons.push(unavailable(definition.id, 'project_disabled', 'Plugin is not enabled for this project'));
continue;
}
if (definition.acquisitionMode === 'user_acquired') {
if (!binding) {
unavailableReasons.push(unavailable(definition.id, 'account_required', 'Marketplace account is required'));
continue;
}
if (!library || library.stale) {
unavailableReasons.push(unavailable(definition.id, 'library_unavailable', 'Marketplace Library is unavailable'));
continue;
}
const entry = libraryById.get(definition.id);
if (!entry || entry.acquisitionMode !== 'user_acquired' || entry.removedAt !== null) {
unavailableReasons.push(unavailable(definition.id, 'library_required', 'Plugin is not acquired in this Account Library'));
continue;
}
// Retired remains usable for an existing Library user. Suspension is
// a runtime gate and is intentionally independent from catalogStatus.
if (entry.runtimeStatus === 'suspended') {
unavailableReasons.push(unavailable(definition.id, 'runtime_suspended', 'Plugin runtime is suspended'));
continue;
}
if (!definition.releaseId) {
unavailableReasons.push(unavailable(definition.id, 'release_invalid', 'Installed Plugin Release is invalid'));
continue;
}
}
if (definition.requiresBackend) {
const pluginPolicy = policyState.catalog?.plugins.find(({ plugin_id }) => plugin_id === definition.id);
if (policyState.status !== 'current' || !policyState.catalog) {
unavailableReasons.push(unavailable(definition.id, 'policy_unavailable', 'Plugin runtime policy is unavailable'));
continue;
}
if (!pluginPolicy || !pluginPolicy.supported_contract_versions.includes(definition.contractVersion)) {
unavailableReasons.push(unavailable(definition.id, 'policy_unsupported', 'Plugin contract is not supported by runtime policy'));
continue;
}
}
if (definition.releaseId) pluginReleaseIds.push(definition.releaseId);
for (const skill of selectedSkills) {
effectiveSkillIds.push(skill.id);
skillEntries.push({ id: skill.id, entryPath: skill.entryPath });
}
if (!definition.requiresBackend) continue;
for (const operation of definition.operations) {
const policy = policyOperation(
policyState.catalog,
definition,
operation.capabilityId,
operation.operation,
);
if (!policy) continue;
runtimePolicies.push({
pluginId: definition.id,
contractVersion: definition.contractVersion,
capabilityId: operation.capabilityId,
operation: operation.operation,
billing: clone(policy.billing),
});
const tool = operation.toolName
? definition.tools.find(({ name }) => name === operation.toolName)
: undefined;
if (!tool || !selectedSkills.some(({ grants }) => grants.includes(tool.capabilityId))) continue;
if (!policyIsBillingAvailable(policy)) {
unavailableReasons.push(unavailable(definition.id, 'billing_unavailable', 'Plugin billing is unavailable'));
continue;
}
toolDefinitions.push(clone(tool));
}
}
return freezeSnapshot({
accountSessionId: accountSessionId(this.options, binding),
projectId: input.projectId,
pluginReleaseIds: [...new Set(pluginReleaseIds)],
effectiveSkillIds: [...new Set(effectiveSkillIds)],
skillEntries,
toolDefinitions: [...new Map(toolDefinitions.map((tool) => [tool.name, tool])).values()],
runtimePolicies: [...new Map(runtimePolicies.map((policy) => [
`${policy.pluginId}\u0000${policy.capabilityId}\u0000${policy.operation}`,
policy,
])).values()],
unavailableReasons,
});
}
/** Alias kept explicit for callers that name the contract rather than the class. */
resolveEffectivePluginSnapshot(input: EffectivePluginResolverInput): Promise<EffectivePluginSnapshot> {
return this.resolve(input);
}
async getSkillSources(): Promise<readonly EffectivePluginSkillSource[]> {
const definitions = await this.definitionRecords();
// Bundled plugin Skills already have a trusted resource source supplied by
// composition. Returning their manifest-relative `packageRoot` here would
// replace that source with a cwd-relative path in the product projection;
// this seam is exclusively for immutable user-installed package roots.
return Object.freeze(definitions.flatMap(({ definition, installed }) => (
!installed || definition.acquisitionMode !== 'user_acquired'
? []
: definition.skills.map((skill) => ({
id: skill.id,
pluginId: definition.id,
packageRoot: definition.provenance.packageRoot,
directory: path.join(definition.provenance.packageRoot, path.dirname(skill.entryPath)),
entryPath: path.basename(skill.entryPath),
}))
)));
}
getPolicyState(): PluginPolicyClientState {
return this.options.policyClient?.getState() ?? EMPTY_POLICY_STATE;
}
private async definitionRecords(): Promise<DefinitionRecord[]> {
const base = [
...(this.options.definitions ?? []),
...(this.options.getDefinitions ? await this.options.getDefinitions() : []),
];
const records = new Map<string, DefinitionRecord>();
for (const definition of base) {
if (!records.has(definition.id)) records.set(definition.id, { definition, installed: true });
}
for (const definition of this.options.installedDefinitions ?? []) {
records.set(definition.id, { definition, installed: true });
}
const installedIds = new Set<string>();
if (this.options.packageStore) {
const index = await this.options.packageStore.readInstalledIndex();
for (const record of index) installedIds.add(record.pluginId);
}
for (const definition of [...records.values()].map(({ definition }) => definition)) {
if (definition.acquisitionMode !== 'user_acquired') continue;
let installed = (this.options.installedDefinitions ?? []).some(({ id }) => id === definition.id);
if (installedIds.has(definition.id)) installed = Boolean(await this.options.packageStore?.getInstalled(definition.id));
if (this.options.getInstalled) installed = Boolean(await this.options.getInstalled(definition.id));
if (!this.options.packageStore && !this.options.getInstalled
&& !(this.options.installedDefinitions ?? []).some(({ id }) => id === definition.id)) {
installed = false;
}
records.set(definition.id, { definition, installed });
}
if (this.options.packageStore) {
for (const pluginId of installedIds) {
if (records.has(pluginId)) continue;
const installed = await this.options.packageStore.getInstalled(pluginId);
if (installed) records.set(pluginId, { definition: installed.definition, installed: true });
}
}
return [...records.values()];
}
private async libraryFor(
binding: AccountBinding | null,
required: boolean,
): Promise<MarketplaceLibrarySnapshot | null> {
if (!required || !binding) return null;
const key = accountBindingKey(binding);
const cached = this.options.accountCache?.getLibrary(binding) ?? this.cachedLibraries.get(key);
if (cached && !cached.stale) this.cachedLibraries.set(key, clone(cached));
try {
const library = this.options.getLibrary
? await this.options.getLibrary(binding)
: this.options.marketplace
? await this.options.marketplace.readLibrary()
: cached ?? null;
if (library) this.cachedLibraries.set(key, clone(library));
return library ?? cached ?? null;
} catch {
return cached ?? null;
}
}
private async enabledPluginIds(projectPath: string): Promise<readonly string[]> {
if (this.options.getEnabledPluginIds) return await this.options.getEnabledPluginIds(projectPath);
if (this.options.projectPlugins) return await this.options.projectPlugins.getEnabledPluginIds(projectPath);
return [];
}
}
export function createEffectivePluginResolver(
options: EffectivePluginResolverOptions,
): EffectivePluginResolver {
return new EffectivePluginResolver(options);
}
export const createEffectiveResolver = createEffectivePluginResolver;
export const resolveEffectivePluginSnapshot = async (
options: EffectivePluginResolverOptions,
input: EffectivePluginResolverInput,
): Promise<EffectivePluginSnapshot> => await createEffectivePluginResolver(options).resolve(input);

View File

@@ -572,7 +572,6 @@ export class PluginPackageStore {
...this.accountCache.referencedReleaseIds(),
...(this.activeWorkerReleaseIds() ?? []),
...this.activeWorkers,
latest.releaseId,
]);
const removable = records.filter((record) => !protectedIds.has(record.releaseId));
if (removable.length === 0) return { status: 'kept', pluginId: validated, releaseId: latest.releaseId, version: latest.version };

View File

@@ -14,6 +14,10 @@ import type {
PluginCatalogOperation,
PluginPolicyClientState,
} from '../services/plugin-policy-client';
import type {
EffectivePluginResolver,
EffectivePluginSnapshot,
} from './effective-resolver';
const MAX_REQUEST_ID = 128;
const PLUGIN_ID_PATTERN = /^[a-z][a-z0-9.-]{0,47}$/u;
@@ -93,6 +97,10 @@ export interface ResolvedWorkerResources {
effectiveSkillIds: readonly string[];
skillEntries: readonly { id: string; entryPath: string }[];
tools: readonly CodingPluginToolDefinition[];
/** The exact Main-owned snapshot used to produce these legacy fields. */
effectiveSnapshot?: EffectivePluginSnapshot;
/** Trusted package roots paired with the same effective snapshot. */
skillRoots?: readonly string[];
}
export interface CodingCapabilityRegistryPort {
@@ -100,7 +108,14 @@ export interface CodingCapabilityRegistryPort {
projectPath: string;
assignedSkillIds: readonly string[];
role: 'parent' | 'child';
projectId?: string;
}): Promise<ResolvedWorkerResources>;
resolveEffectivePluginSnapshot?(input: {
projectId: string;
projectPath: string;
assignedSkillIds: readonly string[];
role: 'parent' | 'child';
}): Promise<EffectivePluginSnapshot>;
invoke(input: {
toolName: string;
context: PiProductToolContext;
@@ -124,6 +139,7 @@ export interface CodingCapabilityRegistryOptions {
adapters: readonly CodingPluginAdapter[];
definitions: readonly CodingPluginDefinition[];
getDurableProjectId?: (projectPath: string, localProjectId: string) => Promise<string> | string;
effectiveResolver?: EffectivePluginResolver;
}
function isRecord(value: unknown): value is Record<string, unknown> {
@@ -257,10 +273,14 @@ function definitionValid(definition: CodingPluginDefinition): boolean {
if (!PLUGIN_ID_PATTERN.test(definition.id) || !VERSION_PATTERN.test(definition.version)
|| !Number.isSafeInteger(definition.contractVersion) || definition.contractVersion < 1
|| definition.scope !== 'project' || typeof definition.requiresBackend !== 'boolean') return false;
if (!definition.skills.length || !definition.tools.length) return false;
if (!definition.skills.length
|| (definition.runtimeKind !== 'skill_only' && !definition.tools.length)
|| (definition.runtimeKind === 'skill_only' && (definition.requiresBackend || definition.tools.length > 0))) return false;
const skillIds = new Set<string>();
for (const skill of definition.skills) {
if (!skill.id || skillIds.has(skill.id) || !skill.entryPath || skill.grants.length === 0) return false;
if (!skill.id || skillIds.has(skill.id) || !skill.entryPath
|| (definition.runtimeKind !== 'skill_only' && skill.grants.length === 0)
|| (definition.runtimeKind === 'skill_only' && skill.grants.length > 0)) return false;
skillIds.add(skill.id);
}
const toolNames = new Set<string>();
@@ -358,7 +378,35 @@ export class CodingCapabilityRegistryImpl implements CodingCapabilityRegistryPor
projectPath: string;
assignedSkillIds: readonly string[];
role: 'parent' | 'child';
projectId?: string;
}): Promise<ResolvedWorkerResources> {
if (this.options.effectiveResolver) {
const snapshot = await this.options.effectiveResolver.resolve({
projectId: input.projectId ?? input.projectPath,
projectPath: input.projectPath,
assignedSkillIds: input.assignedSkillIds,
role: input.role,
});
const sources = await this.options.effectiveResolver.getSkillSources();
const effectiveSkills = new Set(snapshot.effectiveSkillIds);
const pluginIds = new Set<string>(snapshot.runtimePolicies.map(({ pluginId }) => pluginId));
const skillRoots = new Set<string>();
for (const source of sources) {
if (effectiveSkills.has(source.id)) {
pluginIds.add(source.pluginId);
skillRoots.add(source.packageRoot);
}
}
return {
catalogRevision: this.options.effectiveResolver.getPolicyState().revision,
pluginIds: [...pluginIds],
effectiveSkillIds: [...snapshot.effectiveSkillIds],
skillEntries: snapshot.skillEntries.map(({ id, entryPath }) => ({ id, entryPath })),
tools: snapshot.toolDefinitions.map((tool) => structuredClone(tool)),
effectiveSnapshot: snapshot,
skillRoots: [...skillRoots],
};
}
const assigned = [...new Set(input.assignedSkillIds)];
const coreIds = new Set<string>(CORE_CODING_SKILL_IDS);
const pluginSkillOwners = new Map<string, CodingPluginDefinition>();
@@ -432,6 +480,26 @@ export class CodingCapabilityRegistryImpl implements CodingCapabilityRegistryPor
};
}
async resolveEffectivePluginSnapshot(input: {
projectId: string;
projectPath: string;
assignedSkillIds: readonly string[];
role: 'parent' | 'child';
}): Promise<EffectivePluginSnapshot> {
if (this.options.effectiveResolver) return await this.options.effectiveResolver.resolve(input);
const resources = await this.resolveWorkerResources(input);
return Object.freeze({
accountSessionId: 'anonymous',
projectId: input.projectId,
pluginReleaseIds: Object.freeze([]),
effectiveSkillIds: Object.freeze([...resources.effectiveSkillIds]),
skillEntries: Object.freeze(resources.skillEntries.map((entry) => Object.freeze({ ...entry }))),
toolDefinitions: Object.freeze(resources.tools.map((tool) => structuredClone(tool))),
runtimePolicies: Object.freeze([]),
unavailableReasons: Object.freeze([]),
});
}
async invoke(input: {
toolName: string;
context: PiProductToolContext;
@@ -440,10 +508,36 @@ export class CodingCapabilityRegistryImpl implements CodingCapabilityRegistryPor
value: unknown;
}): Promise<PiProductToolResult> {
const indexed = this.toolsByName.get(input.toolName);
const state = this.options.policyClient.getState();
const validId = requestId(input.context) !== 'invalid-request-id';
if (!indexed) return this.unknownResult(input.context, 'plugin_backend_unavailable', 'Plugin capability is unavailable');
const { definition, tool } = indexed;
if (this.options.effectiveResolver && input.context.effectiveSnapshot) {
const currentSnapshot = await this.options.effectiveResolver.resolve({
projectId: input.context.projectId,
projectPath: input.context.projectPath,
assignedSkillIds: input.context.effectiveSnapshot.effectiveSkillIds,
role: input.workerRole,
});
if (currentSnapshot.accountSessionId !== input.context.effectiveSnapshot.accountSessionId
|| !currentSnapshot.toolDefinitions.some(({ name }) => name === input.toolName)) {
const disabled = currentSnapshot.unavailableReasons.some((reason) => (
reason.pluginId === definition.id && reason.code === 'project_disabled'
));
return this.resultFailure(
definition,
tool,
input.context,
disabled ? 'plugin_not_enabled' : 'plugin_runtime_stale',
disabled ? 'Plugin is not enabled for this project' : 'Plugin worker resources are stale',
disabled ? 403 : 409,
false,
policyNotStarted('unknown'),
);
}
}
// The effective resolver may refresh a stale policy while checking the
// worker snapshot. Read the post-resolution state for invocation checks.
const state = this.options.policyClient.getState();
const currentEnabled = await this.enabledPluginIds(input.context.projectPath);
const catalogPolicy = state.catalog ? policyOperation(state.catalog, definition, tool) : null;
const baseBilling = catalogPolicy ? policyNotStarted(catalogPolicy.billing.mode) : policyNotStarted('unknown');