feat: add deployable alpha silo

This commit is contained in:
2026-07-11 00:25:45 +08:00
parent 44557da499
commit 9e954790dc
57 changed files with 2792 additions and 400 deletions
+96 -13
View File
@@ -10,7 +10,10 @@ import { createDatabaseRuntimeSettings } from "./settings/runtime.js";
import { verifyStoredProviderEnvelopes } from "./connections/providerConnections.js";
import { openProviderProxyLease } from "./connections/providerProxy.js";
import { verifyStoredFeishuApplicationEnvelopes } from "./connections/feishuApplicationConnections.js";
import { resolveActiveFeishuApplication } from "./connections/feishuApplicationConnections.js";
import { readServerBinding } from "./settings/server.js";
import { readSiloOrganizationId, requireSiloOrganization } from "./deployment/silo.js";
import { SiloFixedWindowRateLimiter } from "./deployment/siloRateLimit.js";
function requireEnv(name: string): string {
const value = process.env[name];
@@ -30,7 +33,23 @@ function booleanEnv(name: string, fallback: boolean): boolean {
export async function startHub(): Promise<void> {
requireEnv("DATABASE_URL");
const projectWorkspaceRoot = requireEnv("HUB_PROJECT_WORKSPACE_ROOT");
const app = Fastify({ logger: true });
const httpBodyLimit = positiveIntegerEnv("HUB_HTTP_BODY_LIMIT_BYTES");
const maxFilesPerMessage = positiveIntegerEnv("HUB_MAX_FILES_PER_MESSAGE");
const maxBytesPerFile = positiveIntegerEnv("HUB_MAX_FILE_BYTES");
const httpRequestsPerMinute = positiveIntegerEnv("HUB_HTTP_REQUESTS_PER_MINUTE");
const feishuEventsPerMinute = positiveIntegerEnv("HUB_FEISHU_EVENTS_PER_MINUTE");
const app = Fastify({ logger: true, bodyLimit: httpBodyLimit });
const requestLimiter = new SiloFixedWindowRateLimiter(httpRequestsPerMinute, 60_000);
app.addHook("onRequest", async (request, reply) => {
if (request.url === "/api/healthz") return;
const decision = requestLimiter.consume();
if (!decision.allowed) {
await reply
.header("Retry-After", String(decision.retryAfterSeconds))
.status(429)
.send({ error: { code: "rate_limited", message: "Silo request rate exceeded" } });
}
});
const abandonedStages = await removeAbandonedMessageResourceStages(projectWorkspaceRoot);
app.log.info({ removed: abandonedStages }, "startup: removed abandoned Feishu resource stages");
@@ -59,9 +78,39 @@ export async function startHub(): Promise<void> {
}),
);
const feishuAppId = requireEnv("FEISHU_APP_ID");
const feishuAppSecret = requireEnv("FEISHU_APP_SECRET");
const feishuBotOpenId = requireEnv("FEISHU_BOT_OPEN_ID");
const siloOrganizationId = readSiloOrganizationId();
const siloOrganization = await requireSiloOrganization(prisma, siloOrganizationId);
const activeProvider = await prisma.organizationProviderConnection.findUnique({
where: {
organizationId_providerId: {
organizationId: siloOrganization.id,
providerId: "openrouter",
},
},
select: {
status: true,
activeSecretVersion: { select: { connectionId: true, retiredAt: true } },
id: true,
},
});
if (activeProvider?.status !== "ACTIVE" || activeProvider.activeSecretVersion === null ||
activeProvider.activeSecretVersion.connectionId !== activeProvider.id ||
activeProvider.activeSecretVersion.retiredAt !== null) {
throw new Error(`Silo Organization ${siloOrganization.id} has no ACTIVE openrouter Provider Connection`);
}
const feishuApplication = await resolveActiveFeishuApplication(
prisma,
secretEnvelope,
{ organizationId: siloOrganization.id },
);
app.log.info(
{
organizationId: siloOrganization.id,
organizationSlug: siloOrganization.slug,
feishuConnectionId: feishuApplication.connectionId,
},
"startup: proved Silo Organization and resolved Feishu Application",
);
const sessionSecret = requireEnv("HUB_SESSION_SECRET");
const publicBaseUrl = process.env["HUB_PUBLIC_BASE_URL"] ?? "http://127.0.0.1:8788";
const bind = readServerBinding();
@@ -74,24 +123,30 @@ export async function startHub(): Promise<void> {
});
app.log.info("startup: cleared stale locks + dead runs");
app.get("/api/healthz", async () => ({ ok: true, ts: Date.now() }));
let feishuRuntime: { readonly isListenerReady?: () => boolean } | undefined;
app.get("/api/healthz", async (_request, reply) => {
const feishuReady = feishuRuntime?.isListenerReady?.() ?? !booleanEnv("HUB_FEISHU_LISTENER_ENABLED", true);
if (!feishuReady) return reply.status(503).send({ ok: false, feishuReady, ts: Date.now() });
return { ok: true, feishuReady, ts: Date.now() };
});
await registerAdminPlugin(app, {
prisma,
sessionSecret,
publicBaseUrl,
feishuAppId,
feishuAppSecret,
feishuAppId: feishuApplication.appId,
feishuAppSecret: feishuApplication.appSecret,
projectWorkspaceRoot,
secretEnvelope,
});
const address = await app.listen(bind);
app.log.info({ address }, "hub listening");
const feishuListenerEnabled = booleanEnv("HUB_FEISHU_LISTENER_ENABLED", true);
if (feishuListenerEnabled) {
const feishuConfig = { appId: feishuAppId, appSecret: feishuAppSecret, botOpenId: feishuBotOpenId };
const feishuConfig = {
appId: feishuApplication.appId,
appSecret: feishuApplication.appSecret,
botOpenId: feishuApplication.botOpenId,
};
const larkClient = createLarkClient(feishuConfig);
const triggerQueuePurgeTimer = setInterval(() => {
const removed = triggerQueue.purgeExpired();
@@ -100,9 +155,37 @@ export async function startHub(): Promise<void> {
}
}, 60_000);
triggerQueuePurgeTimer.unref();
const trigger = makeTriggerHandler({ prisma, settings: runtimeSettings, logger: app.log, projectWorkspaceRoot });
startFeishuListenerWithClient(feishuConfig, larkClient, app.log, trigger, trigger.onCardAction);
const trigger = makeTriggerHandler({
prisma,
settings: runtimeSettings,
logger: app.log,
projectWorkspaceRoot,
rejectWhenBusy: true,
resourceLimits: { maxFilesPerMessage, maxBytesPerFile },
allowLegacyFeishuIdentity: false,
maxFeishuEventsPerMinute: feishuEventsPerMinute,
});
feishuRuntime = await startFeishuListenerWithClient(
feishuConfig,
larkClient,
app.log,
trigger,
trigger.onCardAction,
() => {
process.exitCode = 1;
void app.close().catch((error) => app.log.error({ err: error }, "Hub close after Feishu failure failed"));
},
);
} else {
app.log.info("feishu listener disabled by HUB_FEISHU_LISTENER_ENABLED");
}
const address = await app.listen(bind);
app.log.info({ address }, "hub listening");
}
function positiveIntegerEnv(name: string): number {
const raw = requireEnv(name);
const value = Number(raw);
if (!Number.isSafeInteger(value) || value <= 0) throw new Error(`${name} must be a positive safe integer`);
return value;
}