Revert "merge: integrate extension auto-update"

This reverts commit 322475860a, reversing
changes made to f52d9d7413.
This commit is contained in:
inman
2026-09-03 16:45:14 +08:00
parent b2e33e2e5d
commit 81a0cdac8e
47 changed files with 169 additions and 3108 deletions

View File

@@ -44,11 +44,6 @@ import {
normalizeRequestId,
writeEmergencyDiagnostic
} from './diagnostics.js';
import {
ExtensionUpdateError,
createExtensionUpdateService,
type ExtensionUpdateController
} from './extension-updates.js';
export function aiServiceConnected(databaseIsReady: boolean, probe: Record<string, unknown>): boolean {
return databaseIsReady && probe.configured === true && probe.reachable === true;
@@ -69,8 +64,6 @@ const accountCreateSchema = z.object({
password: z.string().min(1),
role: z.enum(['admin', 'team_lead', 'user']).default('user'),
erp_account: z.string().trim().max(200).optional(),
extension_ecs_region_id: z.string().trim().max(64).optional(),
extension_ecs_instance_id: z.string().trim().max(80).optional(),
business_route_ids: z.array(
z.string().trim().refine((routeId) => Boolean(businessRouteById(routeId)), '业务类型不存在。')
).max(BUSINESS_ROUTES.length).default([])
@@ -80,16 +73,8 @@ const accountCreateSchema = z.object({
const accountUpdateSchema = z.object({
role: z.enum(['admin', 'team_lead', 'user']).optional(),
is_active: z.boolean().optional(),
erp_account: z.string().trim().max(200).nullable().optional(),
extension_ecs_region_id: z.string().trim().max(64).nullable().optional(),
extension_ecs_instance_id: z.string().trim().max(80).nullable().optional()
}).refine((body) => (
body.role !== undefined
|| body.is_active !== undefined
|| body.erp_account !== undefined
|| body.extension_ecs_region_id !== undefined
|| body.extension_ecs_instance_id !== undefined
), {
erp_account: z.string().trim().max(200).nullable().optional()
}).refine((body) => body.role !== undefined || body.is_active !== undefined || body.erp_account !== undefined, {
message: '至少提供一个账号更新字段。'
});
@@ -142,12 +127,8 @@ const heartbeatSchema = z.object({
extension_version: z.string().max(80).optional(),
erp_account: z.string().trim().max(200).optional(),
erp_account_matched: z.boolean().optional(),
extension_update_safe: z.boolean().optional(),
metadata: z.record(z.unknown()).optional()
});
const extensionReleasePublishSchema = z.object({
package_base64: z.string().min(4).max(70_000_000)
});
const automationSettingsSchema = z.object({ enabled: z.boolean() });
const parserRoutingUpdateSchema = z.object({
mode: z.enum(['ai', 'shadow', 'auto', 'program']),
@@ -415,14 +396,12 @@ export async function buildServer({
config = loadConfig(),
parser,
startParserLoop = true,
loggerDestination,
extensionUpdates: extensionUpdatesOverride
loggerDestination
}: {
config?: AppConfig;
parser?: ExternalParser;
startParserLoop?: boolean;
loggerDestination?: DestinationStream;
extensionUpdates?: ExtensionUpdateController;
} = {}) {
const requestStartedAt = new WeakMap<FastifyRequest, bigint>();
const app = Fastify({
@@ -430,11 +409,7 @@ export async function buildServer({
logController: new LogController({ disableRequestLogging: true }),
genReqId: (rawRequest) => normalizeRequestId(rawRequest.headers['x-request-id']),
trustProxy: true,
bodyLimit: Math.min(75_000_000, Math.max(
2_000_000,
Math.ceil(config.ARTIFACT_MAX_BYTES * 1.4) + 1_000_000,
Math.ceil(config.EXTENSION_UPDATE_MAX_PACKAGE_BYTES * 1.4) + 1_000_000
))
bodyLimit: Math.min(75_000_000, Math.max(2_000_000, Math.ceil(config.ARTIFACT_MAX_BYTES * 1.4) + 1_000_000))
});
await app.register(cookie);
await app.register(helmet, { contentSecurityPolicy: false });
@@ -536,11 +511,6 @@ export async function buildServer({
warn: (metadata, message) => app.log.warn(metadata, message),
error: (metadata, message) => app.log.error(metadata, message)
});
const extensionUpdates = extensionUpdatesOverride || createExtensionUpdateService(config, {
info: (metadata, message) => app.log.info(metadata, message),
warn: (metadata, message) => app.log.warn(metadata, message),
error: (metadata, message) => app.log.error(metadata, message)
});
const externalParser = parser || await loadExternalParser();
const parserOrchestrator = new ParserOrchestrator(externalParser);
const activeParseWorkers = new Map<string, string>();
@@ -1013,8 +983,6 @@ export async function buildServer({
password: body.password,
role: body.role,
erpAccount: body.erp_account,
extensionEcsRegionId: body.extension_ecs_region_id,
extensionEcsInstanceId: body.extension_ecs_instance_id,
businessRouteIds: body.business_route_ids
}, requestId(request));
return { ok: true, account };
@@ -1028,9 +996,7 @@ export async function buildServer({
const account = await auth.updateAccount(session.user, userId, {
role: body.role,
isActive: body.is_active,
erpAccount: body.erp_account,
extensionEcsRegionId: body.extension_ecs_region_id,
extensionEcsInstanceId: body.extension_ecs_instance_id
erpAccount: body.erp_account
}, requestId(request));
await agentBus?.reload();
return { ok: true, account };
@@ -1395,77 +1361,13 @@ export async function buildServer({
contextFor(session, request),
body.connection_id,
body.extension_version || '',
{
...(body.metadata || {}),
extension_update_safe: body.extension_update_safe === true
},
body.metadata || {},
{
erpAccount: body.erp_account || '',
erpAccountMatched: body.erp_account_matched === true
}
);
const { extension_update_target: updateTarget, ...publicWorker } = worker;
const extensionUpdate = await extensionUpdates.observeHeartbeat({
organizationId: session.user.organizationId,
userId: session.user.id,
connectionId: body.connection_id,
currentVersion: body.extension_version || '',
extensionUpdateSafe: body.extension_update_safe === true,
target: {
ecsRegionId: updateTarget.ecs_region_id,
ecsInstanceId: updateTarget.ecs_instance_id,
serverUpdateSafe: updateTarget.server_update_safe
}
});
return { ok: true, connected: true, ...publicWorker, extension_update: extensionUpdate };
});
app.get('/api/extension-updates/releases', async (request) => {
const session = await requireAdminSession(request);
return {
ok: true,
enabled: config.EXTENSION_AUTO_UPDATE_ENABLED,
releases: await extensionUpdates.listReleases(session.user.organizationId)
};
});
app.post('/api/extension-updates/releases', {
config: { rateLimit: { max: 5, timeWindow: '1 minute' } }
}, async (request) => {
const session = await requireAdminMutationSession(request);
const body = extensionReleasePublishSchema.parse(request.body);
const normalized = body.package_base64.replace(/\s+/gu, '');
if (!/^[A-Za-z0-9+/]+={0,2}$/u.test(normalized)) {
throw new ExtensionUpdateError('extension_package_base64_invalid', '插件包编码无效。');
}
const content = Buffer.from(normalized, 'base64');
const canonical = content.toString('base64').replace(/=+$/u, '');
if (canonical !== normalized.replace(/=+$/u, '')) {
throw new ExtensionUpdateError('extension_package_base64_invalid', '插件包编码无效。');
}
return {
ok: true,
release: await extensionUpdates.publishRelease({
organizationId: session.user.organizationId,
actorUserId: session.user.id,
requestId: requestId(request),
content
})
};
});
app.get('/api/extension-updates/package/:token', {
// A fleet may share one outbound NAT address and start together after a
// release. The HMAC token is already release/organization/host/expiry
// scoped, so keep only a generous abuse ceiling here.
config: { rateLimit: { max: 300, timeWindow: '1 minute' } }
}, async (request, reply) => {
const params = request.params as { token: string };
const download = await extensionUpdates.downloadPackage(params.token);
reply.header('Cache-Control', 'private, no-store, max-age=0');
reply.header('Content-Type', 'application/zip');
reply.header('Content-Disposition', attachmentContentDisposition(download.fileName));
return reply.send(download.content);
return { ok: true, connected: true, ...worker };
});
app.get('/api/audit', async (request) => {
@@ -1586,7 +1488,7 @@ export async function buildServer({
});
app.setErrorHandler((error, request, reply) => {
if (error instanceof AuthError || error instanceof TaskError || error instanceof ExtensionUpdateError) {
if (error instanceof AuthError || error instanceof TaskError) {
request.log.warn({
diagnostic_event: 'http.request.rejected',
diagnostic_stage: 'http',
@@ -1633,14 +1535,13 @@ export async function buildServer({
}
app.addHook('onClose', async () => {
extensionUpdates.close();
app.log.info({
diagnostic_event: 'service.closing',
diagnostic_stage: 'shutdown'
}, 'control plane closing');
await closePool();
});
return { app, auth, tasks, agentBus, channelService, extensionUpdates };
return { app, auth, tasks, agentBus, channelService };
}
function installProcessDiagnostics(app: Awaited<ReturnType<typeof buildServer>>['app']): void {