fix(gateway): trim startup imports

This commit is contained in:
Vincent Koc
2026-04-26 22:47:56 -07:00
parent 06b3e4ef8a
commit b02cca4e00
8 changed files with 395 additions and 274 deletions

View File

@@ -1,9 +1,10 @@
import { listChannelPlugins } from "../channels/plugins/index.js";
import type { OutboundSendDeps } from "../infra/outbound/send-deps.js";
import { createLazyRuntimeSurface } from "../shared/lazy-runtime.js";
import type { CliDeps } from "./deps.types.js";
import { createOutboundSendDepsFromCliSource } from "./outbound-send-mapping.js";
import { createChannelOutboundRuntimeSend } from "./send-runtime/channel-outbound-send.js";
import {
CLI_OUTBOUND_SEND_FACTORY,
createOutboundSendDepsFromCliSource,
} from "./outbound-send-mapping.js";
/**
* Lazy-loaded per-channel send functions, keyed by channel ID.
@@ -17,6 +18,35 @@ type RuntimeSendModule = {
runtimeSend: RuntimeSend;
};
const NON_CHANNEL_DEP_KEYS = new Set([
"__proto__",
"constructor",
"cron",
"cronConfig",
"cronEnabled",
"defaultAgentId",
"enqueueSystemEvent",
"getQueueSize",
"hasOwnProperty",
"inspect",
"log",
"migrateOrphanedSessionKeys",
"nowMs",
"onEvent",
"requestHeartbeatNow",
"resolveSessionStorePath",
"runHeartbeatOnce",
"runIsolatedAgentJob",
"runtime",
"sendCronFailureAlert",
"sessionStorePath",
"storePath",
"then",
"toJSON",
"toString",
"valueOf",
]);
// Per-channel module caches for lazy loading.
const senderCache = new Map<string, Promise<RuntimeSend>>();
@@ -41,22 +71,40 @@ function createLazySender(
}
export function createDefaultDeps(): CliDeps {
// Keep the default dependency barrel limited to lazy senders so callers that
// only need outbound deps do not pull channel runtime boundaries on import.
const deps: CliDeps = {};
for (const plugin of listChannelPlugins()) {
deps[plugin.id] = createLazySender(
plugin.id,
async () =>
({
runtimeSend: createChannelOutboundRuntimeSend({
channelId: plugin.id,
unavailableMessage: `${plugin.meta.label ?? plugin.id} outbound adapter is unavailable.`,
}) as RuntimeSend,
}) satisfies RuntimeSendModule,
);
}
return deps;
const resolveSender = (channelId: string) =>
createLazySender(channelId, async () => {
const { createChannelOutboundRuntimeSend } =
await import("./send-runtime/channel-outbound-send.js");
return {
runtimeSend: createChannelOutboundRuntimeSend({
channelId: channelId as import("../channels/plugins/types.public.js").ChannelId,
unavailableMessage: `${channelId} outbound adapter is unavailable.`,
}) as RuntimeSend,
} satisfies RuntimeSendModule;
});
Object.defineProperty(deps, CLI_OUTBOUND_SEND_FACTORY, {
configurable: false,
enumerable: false,
value: resolveSender,
writable: false,
});
return new Proxy(deps, {
get(target, property, receiver) {
if (typeof property !== "string") {
return Reflect.get(target, property, receiver);
}
const existing = Reflect.get(target, property, receiver);
if (existing !== undefined || NON_CHANNEL_DEP_KEYS.has(property)) {
return existing;
}
const sender = resolveSender(property);
Reflect.set(target, property, sender, receiver);
return sender;
},
});
}
export function createOutboundSendDeps(deps: CliDeps): OutboundSendDeps {

View File

@@ -1,4 +1,3 @@
import { normalizeAnyChannelId } from "../channels/registry.js";
import {
resolveLegacyOutboundSendDepKeys,
type OutboundSendDeps,
@@ -9,7 +8,15 @@ import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
* CLI-internal send function sources, keyed by channel ID.
* Each value is a lazily-loaded send function for that channel.
*/
export type CliOutboundSendSource = { [channelId: string]: unknown };
export const CLI_OUTBOUND_SEND_FACTORY: unique symbol = Symbol.for(
"openclaw.cliOutboundSendFactory",
) as never;
export type CliOutboundSendFactory = (channelId: string) => unknown;
export type CliOutboundSendSource = {
[channelId: string]: unknown;
[CLI_OUTBOUND_SEND_FACTORY]?: CliOutboundSendFactory;
};
function normalizeLegacyChannelStem(raw: string): string {
const normalized = normalizeLowercaseStringOrEmpty(
@@ -27,7 +34,16 @@ function resolveChannelIdFromLegacySourceKey(key: string): string | undefined {
return undefined;
}
const normalizedStem = normalizeLegacyChannelStem(match[1] ?? "");
return normalizeAnyChannelId(normalizedStem) ?? (normalizedStem || undefined);
return normalizedStem || undefined;
}
function resolveChannelIdFromLegacyOutboundKey(key: string): string | undefined {
const match = key.match(/^send(.+)$/);
if (!match) {
return undefined;
}
const normalizedStem = normalizeLegacyChannelStem(match[1] ?? "");
return normalizedStem || undefined;
}
/**
@@ -36,6 +52,7 @@ function resolveChannelIdFromLegacySourceKey(key: string): string | undefined {
*/
export function createOutboundSendDepsFromCliSource(deps: CliOutboundSendSource): OutboundSendDeps {
const outbound: OutboundSendDeps = { ...deps };
const sendFactory = deps[CLI_OUTBOUND_SEND_FACTORY];
for (const legacySourceKey of Object.keys(deps)) {
const channelId = resolveChannelIdFromLegacySourceKey(legacySourceKey);
@@ -60,5 +77,36 @@ export function createOutboundSendDepsFromCliSource(deps: CliOutboundSendSource)
}
}
return outbound;
if (!sendFactory) {
return outbound;
}
const resolveFactoryValue = (key: string): unknown => {
const channelId =
outbound[key] === undefined ? (resolveChannelIdFromLegacyOutboundKey(key) ?? key) : key;
if (!channelId || channelId === "then" || channelId === "toJSON") {
return undefined;
}
const value = sendFactory(channelId);
if (value !== undefined) {
outbound[channelId] = value;
for (const legacyDepKey of resolveLegacyOutboundSendDepKeys(channelId)) {
outbound[legacyDepKey] ??= value;
}
}
return value;
};
return new Proxy(outbound, {
get(target, property, receiver) {
if (typeof property !== "string") {
return Reflect.get(target, property, receiver);
}
const existing = Reflect.get(target, property, receiver);
if (existing !== undefined) {
return existing;
}
return resolveFactoryValue(property);
},
});
}