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

@@ -16,6 +16,7 @@ import {
import type { PiProductTools } from './product-tools';
import type { CodingPluginToolDefinition } from '../../../shared/coding-plugins';
import type { PiSkillEntry } from './resource-loader';
import type { EffectivePluginSnapshot } from '../../coding-plugins/effective-resolver';
const MAX_REQUEST_BYTES = 64 * 1024;
const PRODUCT_TOOL_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9._:-]{0,63}$/u;
@@ -40,6 +41,7 @@ interface WorkerRegistrationRecord {
tools: CodingPluginToolDefinition[];
projectWriteLeaseToolNames: string[];
role: 'parent' | 'child';
effectiveSnapshot?: EffectivePluginSnapshot;
contextFile: string;
runId: string | null;
leases: Map<string, PiProjectWriteLease>;
@@ -62,6 +64,8 @@ export interface RegisterPiExtensionWorkerInput {
skillEntries?: readonly PiSkillEntry[];
catalogRevision?: number;
tools?: readonly CodingPluginToolDefinition[];
/** Exact Main-owned resolver output used for this worker generation. */
effectiveSnapshot?: EffectivePluginSnapshot;
extensionsDir: string;
role?: 'parent' | 'child';
runId?: string;
@@ -248,6 +252,7 @@ export class PiManagedExtensionHost {
tools,
projectWriteLeaseToolNames,
role,
...(input.effectiveSnapshot ? { effectiveSnapshot: input.effectiveSnapshot } : {}),
contextFile,
runId: role === 'child'
? input.runId as string
@@ -424,6 +429,7 @@ export class PiManagedExtensionHost {
projectId: record.projectId,
projectPath: record.projectPath,
skillIds: record.skillIds,
...(record.effectiveSnapshot ? { effectiveSnapshot: record.effectiveSnapshot } : {}),
}, value.input);
this.respond(response, 200, { result: productResult });
return;
@@ -589,6 +595,7 @@ export class PiManagedExtensionHost {
role: record.role,
skillIds: record.skillIds,
...(record.catalogRevision === undefined ? {} : { catalogRevision: record.catalogRevision }),
...(record.effectiveSnapshot ? { effectivePluginSnapshot: record.effectiveSnapshot } : {}),
allowedToolNames: record.allowedToolNames,
tools: record.tools,
projectWriteLeaseToolNames: record.projectWriteLeaseToolNames,

View File

@@ -19,7 +19,9 @@ import {
import {
type CodingCapabilityRegistry,
} from '../../coding-plugins/registry';
import type { EffectivePluginSnapshot } from '../../coding-plugins/effective-resolver';
import type { KnownToolDetails, RuntimeContextDetailsV1 } from '../contracts';
import { BUNDLED_CODING_SKILL_IDS } from '../../../shared/coding-skills';
import { PiAgentBrowserTool } from './extensions/agent-browser';
import { reportChangedFiles } from './extensions/changed-file';
import { PiGameAssetTools } from './extensions/game-assets';
@@ -53,6 +55,7 @@ export interface PiProductToolContext {
projectId: string;
projectPath: string;
skillIds: readonly string[];
effectiveSnapshot?: EffectivePluginSnapshot;
}
export interface PiProductToolResult {
@@ -66,6 +69,8 @@ export interface PiProductToolsOptions {
bundledSkillsDir: string;
changeTracker?: ConversationChangeTracker;
pluginSkillSources?: readonly ProductCodingPluginSkillSource[];
getPluginSkillSources?(): readonly ProductCodingPluginSkillSource[]
| Promise<readonly ProductCodingPluginSkillSource[]>;
capabilityRegistry?: CodingCapabilityRegistry;
}
@@ -97,16 +102,27 @@ export class PiProductTools {
return this.changeTracker.getSnapshot(conversationId);
}
listSkills(
async listSkills(
skillIds: readonly string[],
availablePluginSkillIds: readonly string[] = [],
): Promise<ProductCodingSkill[]> {
const available = new Set(availablePluginSkillIds);
const sources = (this.options.pluginSkillSources ?? []).map((source) => ({
const dynamicSources = this.options.getPluginSkillSources
? await this.options.getPluginSkillSources()
: [];
const uniqueSources = [...new Map(
[...(this.options.pluginSkillSources ?? []), ...dynamicSources]
.map((source) => [source.id, source] as const),
).values()];
const sources = uniqueSources.map((source) => ({
...source,
available: available.has(source.pluginId ?? source.id) || available.has(source.id),
}));
return listProductCodingSkills(this.options.bundledSkillsDir, skillIds, sources);
const selectedSkillIds = this.options.getPluginSkillSources
? skillIds.filter((id) => BUNDLED_CODING_SKILL_IDS.includes(id)
|| uniqueSources.some((source) => source.id === id))
: skillIds;
return listProductCodingSkills(this.options.bundledSkillsDir, selectedSkillIds, sources);
}
async listCommands(

View File

@@ -5,6 +5,7 @@ import { validateSessionKey } from '../../coding-projects/conversation-store';
import { resolveBundledCodingPluginRootPaths } from '../../coding-plugins/manifest';
import type { PiProviderSelection } from './provider-config';
import type { PiManagedInputRevision } from './managed-input-revision';
import type { EffectivePluginSnapshot } from '../../coding-plugins/effective-resolver';
const MANAGED_SEGMENT_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;
@@ -38,6 +39,10 @@ export interface MaterializePiAgentResourcesOptions {
skillEntries: readonly PiSkillEntry[];
catalogRevision: number;
bundledSkillsDir: string;
/** Main-owned installed package roots for effective Marketplace Skills. */
skillRoots?: readonly string[];
/** The exact resolver output for this worker generation. */
effectiveSnapshot?: EffectivePluginSnapshot;
revision: PiManagedInputRevision;
}
@@ -50,6 +55,7 @@ export interface PiAgentResourceManifest {
skillEntries: PiSkillEntry[];
catalogRevision: number;
revision: PiManagedInputRevision;
effectivePluginSnapshot?: EffectivePluginSnapshot;
}
export interface PiAgentResourceSnapshot {
@@ -62,6 +68,7 @@ export interface PiAgentResourceSnapshot {
skillPaths: string[];
catalogRevision: number;
revision: PiManagedInputRevision;
effectivePluginSnapshot?: EffectivePluginSnapshot;
summary: {
projectId: string;
agentId: string;
@@ -69,6 +76,7 @@ export interface PiAgentResourceSnapshot {
skillEntries: PiSkillEntry[];
catalogRevision: number;
revision: PiManagedInputRevision;
effectivePluginSnapshot?: EffectivePluginSnapshot;
};
}
@@ -184,6 +192,7 @@ function pathWithin(root: string, entryPath: string): string | null {
async function resolveSkillEntryPath(
bundledSkillsDir: string,
entry: PiSkillEntry,
skillRoots: readonly string[] = [],
): Promise<string> {
const roots = [
path.resolve(bundledSkillsDir),
@@ -191,6 +200,7 @@ async function resolveSkillEntryPath(
path.dirname(path.resolve(bundledSkillsDir)),
'coding-plugins',
)),
...skillRoots.map((root) => path.resolve(root)),
];
for (const root of roots) {
const candidate = pathWithin(root, entry.entryPath);
@@ -208,10 +218,11 @@ async function resolveSkillEntryPath(
export async function resolveExplicitCodingSkillPaths(
bundledSkillsDir: string,
skillEntries: readonly PiSkillEntry[],
skillRoots: readonly string[] = [],
): Promise<{ skillIds: string[]; skillEntries: PiSkillEntry[]; skillPaths: string[] }> {
const normalizedEntries = normalizeSkillEntries(skillEntries);
const skillPaths = await Promise.all(normalizedEntries.map((entry) => (
resolveSkillEntryPath(bundledSkillsDir, entry)
resolveSkillEntryPath(bundledSkillsDir, entry, skillRoots)
)));
return {
skillIds: normalizedEntries.map(({ id }) => id),
@@ -235,6 +246,7 @@ export async function materializePiAgentResources(
const { skillIds, skillEntries, skillPaths } = await resolveExplicitCodingSkillPaths(
options.bundledSkillsDir,
options.skillEntries,
options.skillRoots,
);
const resolvedCatalogRevision = catalogRevision(options.catalogRevision);
const promptPath = path.join(projectPromptsDir, `${agentId}.md`);
@@ -248,6 +260,7 @@ export async function materializePiAgentResources(
skillEntries: structuredClone(skillEntries),
catalogRevision: resolvedCatalogRevision,
revision: { ...options.revision },
...(options.effectiveSnapshot ? { effectivePluginSnapshot: structuredClone(options.effectiveSnapshot) } : {}),
};
await atomicWriteText(promptPath, options.prompt);
await atomicWriteJson(manifestPath, manifest);
@@ -261,6 +274,7 @@ export async function materializePiAgentResources(
skillPaths,
catalogRevision: resolvedCatalogRevision,
revision: { ...options.revision },
...(options.effectiveSnapshot ? { effectivePluginSnapshot: structuredClone(options.effectiveSnapshot) } : {}),
summary: {
projectId,
agentId,
@@ -268,6 +282,7 @@ export async function materializePiAgentResources(
skillEntries: structuredClone(skillEntries),
catalogRevision: resolvedCatalogRevision,
revision: { ...options.revision },
...(options.effectiveSnapshot ? { effectivePluginSnapshot: structuredClone(options.effectiveSnapshot) } : {}),
},
};
}

View File

@@ -141,6 +141,8 @@ export interface PiManagedWorkerOpenerOptions {
cliPath: string;
userDataDir: string;
bundledSkillsDir: string;
getSkillRoots?(): readonly string[] | Promise<readonly string[]>;
registerActivePluginReleases?(releaseIds: readonly string[]): () => void;
loadProviderInput(): Promise<PiManagedProviderInput>;
resolveCredential(account: ProviderAccount): Promise<string | null>;
getLocalProxyCredential?(): Promise<string | undefined>;
@@ -246,6 +248,7 @@ export function createPiManagedWorkerOpener(
const workerResources = options.capabilityRegistry
? await options.capabilityRegistry.resolveWorkerResources({
projectPath: registered.projectPath,
projectId: input.conversation.projectId,
assignedSkillIds: registered.agent.skillIds,
role: 'parent',
})
@@ -258,6 +261,12 @@ export function createPiManagedWorkerOpener(
skillEntries: workerResources.skillEntries,
catalogRevision: workerResources.catalogRevision,
bundledSkillsDir: options.bundledSkillsDir,
...(workerResources.skillRoots
? { skillRoots: workerResources.skillRoots }
: options.getSkillRoots ? { skillRoots: await options.getSkillRoots() } : {}),
...(workerResources.effectiveSnapshot
? { effectiveSnapshot: workerResources.effectiveSnapshot }
: {}),
revision: input.revision,
});
const credential = await buildPiWorkerCredentialProjection({
@@ -276,6 +285,9 @@ export function createPiManagedWorkerOpener(
skillEntries: workerResources.skillEntries,
catalogRevision: workerResources.catalogRevision,
tools: workerResources.tools,
...(workerResources.effectiveSnapshot
? { effectiveSnapshot: workerResources.effectiveSnapshot }
: {}),
extensionsDir: managedPaths.extensionsDir,
});
recordManagedMilestone(
@@ -313,8 +325,21 @@ export function createPiManagedWorkerOpener(
conversationId: input.conversation.conversationId,
workerGeneration: input.generation,
});
const releaseActivePluginReleases = workerResources.effectiveSnapshot
? options.registerActivePluginReleases?.(workerResources.effectiveSnapshot.pluginReleaseIds)
: undefined;
let managedResourcesDisposed = false;
const disposeManagedResources = async (): Promise<void> => {
if (managedResourcesDisposed) return;
managedResourcesDisposed = true;
try {
await extension.dispose();
} finally {
releaseActivePluginReleases?.();
}
};
let unsubscribeExtensionInvalidation = process.subscribeInvalidation(() => {
void extension.dispose();
void disposeManagedResources();
});
try {
const spawnStartedAt = now();
@@ -389,7 +414,7 @@ export function createPiManagedWorkerOpener(
`${input.conversation.conversationId}:${input.generation}`,
input.generation,
process,
extension.dispose,
disposeManagedResources,
() => {
unsubscribeExtensionInvalidation();
unsubscribeExtensionInvalidation = () => undefined;
@@ -400,7 +425,7 @@ export function createPiManagedWorkerOpener(
} catch (error) {
unsubscribeExtensionInvalidation();
await process.stop('open_failure').catch(() => undefined);
await extension.dispose();
await disposeManagedResources();
throw error;
}
};

View File

@@ -56,6 +56,7 @@ export interface PiManagedSubagentChildOpenerOptions {
cliPath: string;
userDataDir: string;
bundledSkillsDir: string;
getSkillRoots?(): readonly string[] | Promise<readonly string[]>;
extensionHost: PiManagedExtensionHost;
loadProviderInput(): Promise<{ accounts: ProviderAccount[]; modelSummaries: ModelSummary[] }>;
resolveCredential(account: ProviderAccount): Promise<string | null>;
@@ -194,6 +195,7 @@ export function createPiManagedSubagentChildOpener(
const workerResources = options.capabilityRegistry
? await options.capabilityRegistry.resolveWorkerResources({
projectPath: project.path,
projectId: input.projectId,
assignedSkillIds: agent.skillIds,
role: 'child',
})
@@ -206,6 +208,12 @@ export function createPiManagedSubagentChildOpener(
skillEntries: workerResources.skillEntries,
catalogRevision: workerResources.catalogRevision,
bundledSkillsDir: options.bundledSkillsDir,
...(workerResources.skillRoots
? { skillRoots: workerResources.skillRoots }
: options.getSkillRoots ? { skillRoots: await options.getSkillRoots() } : {}),
...(workerResources.effectiveSnapshot
? { effectiveSnapshot: workerResources.effectiveSnapshot }
: {}),
revision: options.getRevision(),
});
const credential = await buildPiWorkerCredentialProjection({
@@ -224,6 +232,9 @@ export function createPiManagedSubagentChildOpener(
skillEntries: workerResources.skillEntries,
catalogRevision: workerResources.catalogRevision,
tools: [],
...(workerResources.effectiveSnapshot
? { effectiveSnapshot: workerResources.effectiveSnapshot }
: {}),
extensionsDir: managedPaths.extensionsDir,
role: 'child',
runId: input.runId,