feat: integrate marketplace plugins with coding runtime
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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) } : {}),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user