Merge branch 'main' of https://git.nianxx.cn/wangxuming/makelore
# Conflicts: # electron/coding-runtime/pi/extensions/makelore-runtime.ts
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user