# Conflicts:
#	electron/coding-runtime/pi/extensions/makelore-runtime.ts
This commit is contained in:
2026-08-31 15:06:37 +08:00
128 changed files with 8282 additions and 3026 deletions

View File

@@ -1,3 +1,4 @@
import { accessSync, constants } from 'node:fs';
import path from 'node:path';
import type { AgentBrowserModule } from '../agent-browser';
import { CodingAttachmentStore } from '../coding-projects/attachment-store';
@@ -10,6 +11,7 @@ import {
} from '../coding-projects/project-store';
import { CodingConversationService } from '../coding-runtime/conversation-service';
import { PiManagedExtensionHost } from '../coding-runtime/pi/extension-host';
import { PiAgentServerProcess } from '../coding-runtime/pi/agent-server-process';
import { PiManagedInputRevisionCoordinator } from '../coding-runtime/pi/managed-input-revision';
import { PiProductTools } from '../coding-runtime/pi/product-tools';
import {
@@ -33,8 +35,10 @@ import {
import { createCodingProductHost, type CodingProductComposition } from './coding-product-services';
import { createDataServiceOperations } from '../services/data-service-client';
import { createPreviewDataSessionManager, type PreviewDataSessionManager } from '../services/preview-data-session';
import { archivePiConversationSession } from '../coding-runtime/pi/resource-loader';
import { resolveLegacyProjectModel } from '../coding-projects/legacy-v1';
import {
archivePiConversationSession,
getPiManagedPaths,
} from '../coding-runtime/pi/resource-loader';
import { createProjectPluginService } from '../coding-plugins/project-service';
import {
createCodingCapabilityRegistry,
@@ -71,6 +75,7 @@ import {
export interface CodingCompositionPaths {
executablePath: string;
cliPath: string;
serverPath: string;
userDataDir: string;
bundledSkillsDir: string;
}
@@ -89,14 +94,51 @@ export interface CreateCodingCompositionOptions {
policyClient?: PluginPolicyClient;
}
type PiWorkerExecutableProbe = (candidate: string) => boolean;
function canExecute(candidate: string): boolean {
try {
accessSync(candidate, constants.X_OK);
return true;
} catch {
return false;
}
}
export function resolvePiWorkerExecutablePath(
executablePath: string,
options: {
platform?: NodeJS.Platform;
canExecute?: PiWorkerExecutableProbe;
} = {},
): string {
if ((options.platform ?? process.platform) !== 'darwin') return executablePath;
// The product binary remains a Foreground LaunchServices app even in Node mode.
// Electron's generic Helper is LSUIElement=true, so workers stay out of the Dock.
const executableName = path.basename(executablePath);
const helperExecutablePath = path.resolve(
path.dirname(executablePath),
'..',
'Frameworks',
`${executableName} Helper.app`,
'Contents',
'MacOS',
`${executableName} Helper`,
);
return (options.canExecute ?? canExecute)(helperExecutablePath)
? helperExecutablePath
: executablePath;
}
export function resolveCodingPiRuntimePaths(input: {
isPackaged: boolean;
resourcesPath: string;
appPath: string;
executablePath: string;
}): Pick<CodingCompositionPaths, 'executablePath' | 'cliPath'> {
}): Pick<CodingCompositionPaths, 'executablePath' | 'cliPath' | 'serverPath'> {
return {
executablePath: input.executablePath,
executablePath: resolvePiWorkerExecutablePath(input.executablePath),
cliPath: input.isPackaged
? path.join(input.resourcesPath, 'pi-runtime', 'dist', 'cli.js')
: path.join(
@@ -107,6 +149,9 @@ export function resolveCodingPiRuntimePaths(input: {
'dist',
'cli.js',
),
serverPath: input.isPackaged
? path.join(input.resourcesPath, 'resources', 'pi-agent-server.mjs')
: path.join(input.appPath, 'resources', 'pi-agent-server.mjs'),
};
}
@@ -196,6 +241,12 @@ export function createCodingComposition(
});
const revisions = new PiManagedInputRevisionCoordinator();
const processBudget = new PiProcessBudget();
const agentServer = new PiAgentServerProcess({
executablePath: options.paths.executablePath,
serverPath: options.paths.serverPath,
runtimeRoot: path.dirname(path.dirname(options.paths.cliPath)),
configDir: getPiManagedPaths(options.paths.userDataDir).configDir,
});
const loadProviderInput = async () => ({
accounts: await getProviderService().listAccounts(),
modelSummaries: [],
@@ -204,12 +255,6 @@ export function createCodingComposition(
let plugins: CodingProjectPluginService | undefined;
let previewDataSession: PreviewDataSessionManager | undefined;
const projects = new CodingProjectService(projectStore, {
migration: {
resolveLegacyModel: async ({ legacyModel }) => resolveLegacyProjectModel(
legacyModel,
await getProviderService().listAccounts(),
),
},
createConversationStore: conversationStoreForProject,
onResourcesChanged: async (project) => {
runtime?.markResourcesStale();
@@ -308,6 +353,8 @@ export function createCodingComposition(
});
const workerPool = new PiWorkerPool({
processBudget,
processMode: 'shared',
maxIdle: 8,
revisionCoordinator: revisions,
openWorker: createPiManagedWorkerOpener({
registry,
@@ -321,6 +368,7 @@ export function createCodingComposition(
registerActivePluginReleases,
loadProviderInput,
resolveCredential: resolvePiProviderCredentialFromSecretStore,
createProcess: (processOptions) => agentServer.createWorker(processOptions),
...(getLocalProxyCredential
? { getLocalProxyCredential: async () => getLocalProxyCredential() }
: {}),
@@ -349,7 +397,6 @@ export function createCodingComposition(
const subagents = new PiSubagentScheduler({
openChild: childOpener,
processBudget,
reclaimProcessCapacity: (signal) => workerPool.reclaimIdleWorker(signal),
});
runtime = new PiConversationRuntime({
pool: workerPool,
@@ -443,6 +490,7 @@ export function createCodingComposition(
await Promise.allSettled(conversationIds.map((conversationId) => (
runtime.dispose(conversationId, reason)
)));
await agentServer.stop();
},
async shutdown() {
previewDataSession?.dispose();
@@ -456,9 +504,13 @@ export function createCodingComposition(
await subagents.close();
await runtime.shutdown();
} finally {
unsubscribeMarketplaceSession();
packageStore.dispose();
marketplaceClient.dispose();
try {
await agentServer.stop();
} finally {
unsubscribeMarketplaceSession();
packageStore.dispose();
marketplaceClient.dispose();
}
}
},
};

View File

@@ -63,6 +63,10 @@ type SessionRefreshInput = {
const MAX_AUTH_ERROR_LENGTH = 180;
const MAX_CAPTCHA_IMAGE_BYTES = 1024 * 1024;
const MAX_PUBLIC_USERNAME_LENGTH = 256;
const MAX_PUBLIC_ID_LENGTH = 128;
const MAX_PUBLIC_AUTHORITIES = 100;
const MAX_PUBLIC_AUTHORITY_LENGTH = 128;
const PNG_MAGIC = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu;
@@ -90,6 +94,57 @@ function readOptionalBoolean(value: unknown, fallback: boolean): boolean {
return typeof value === 'boolean' ? value : fallback;
}
function readBoundedString(value: unknown, maxLength: number): string | null {
const normalized = readOptionalTrimmedString(value);
return normalized && normalized.length <= maxLength ? normalized : null;
}
function readPublicStringId(...values: unknown[]): string | null {
for (const value of values) {
if (typeof value === 'number' && Number.isFinite(value)) return String(value);
const normalized = readBoundedString(value, MAX_PUBLIC_ID_LENGTH);
if (normalized) return normalized;
}
return null;
}
function readPublicStringOrNumberId(...values: unknown[]): string | number | null {
for (const value of values) {
if (typeof value === 'number' && Number.isFinite(value)) return value;
const normalized = readBoundedString(value, MAX_PUBLIC_ID_LENGTH);
if (normalized) return normalized;
}
return null;
}
function readPublicAuthorities(value: unknown): string[] {
if (!Array.isArray(value)) return [];
return value
.map((authority) => readBoundedString(authority, MAX_PUBLIC_AUTHORITY_LENGTH))
.filter((authority): authority is string => authority !== null)
.slice(0, MAX_PUBLIC_AUTHORITIES);
}
function projectCurrentUser(profile: Record<string, unknown>): {
username: string;
userId: string | null;
tenantId: string | number | null;
deptId: string | number | null;
authorities: string[];
} | null {
const username = readBoundedString(profile.username, MAX_PUBLIC_USERNAME_LENGTH)
?? readBoundedString(profile.user_name, MAX_PUBLIC_USERNAME_LENGTH);
if (!username) return null;
return {
username,
userId: readPublicStringId(profile.user_id, profile.userId, profile.id),
tenantId: readPublicStringOrNumberId(profile.tenant_id, profile.tenantId),
deptId: readPublicStringOrNumberId(profile.dept_id, profile.deptId),
authorities: readPublicAuthorities(profile.authorities),
};
}
function withoutRefreshToken(payload: unknown): unknown {
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) return payload;
const { refresh_token: _refreshToken, ...publicPayload } = payload as Record<string, unknown>;
@@ -600,6 +655,7 @@ async function handleCurrentUser(res: ServerResponse, ctx: HostApiContext): Prom
: {};
sendJson(res, 200, {
success: true,
user: projectCurrentUser(profile),
moduleAccess: normalizeModuleAccess(profile.module_access),
});
}

View File

@@ -146,16 +146,6 @@ export async function handleCodingProjectRoutes(
});
return true;
}
if (url.pathname === '/api/coding/projects/legacy-conversation-notice/acknowledge'
&& req.method === 'POST') {
const body = await parseJsonBody<{ projectId?: string }>(req);
sendJson(res, 200, {
snapshot: publicProjectSnapshot(
await projects.acknowledgeLegacyConversationNotice(body.projectId ?? ''),
),
});
return true;
}
if (url.pathname === '/api/coding/projects/conversations' && req.method === 'GET') {
sendJson(res, 200, {
conversations: await conversations.listConversations(