Files
openclaw-zero-token/gateway/config-reload-plan.ts
sjhu 571e14a236 feat: upgrade to upstream v2026.3.28
Major upgrade from e26988a38 to upstream v2026.3.28 (f9b107928).
Key changes:
- Upstream src/, ui/, extensions/ (89 bundled extensions)
- Zero-token web providers preserved in src/zero-token/
- AskOnce plugin restored and registered as CLI command
- Added missing packages: @anthropic-ai/vertex-sdk, @modelcontextprotocol/sdk
- Fixed tsconfig rootDir, skipLibCheck for plugin-sdk DTS build
- Added askonce to bundled plugin metadata and package.json exports
- Fixed AskOnce CLI command registration (missing commands metadata)
- Restored AskOnce adapter imports (correct 5-level relative paths)
- Removed stale migration artifacts from root directory
2026-03-30 17:58:12 +08:00

216 lines
5.8 KiB
TypeScript

import { type ChannelId, listChannelPlugins } from "../channels/plugins/index.js";
import { getActivePluginRegistry } from "../plugins/runtime.js";
export type ChannelKind = ChannelId;
export type GatewayReloadPlan = {
changedPaths: string[];
restartGateway: boolean;
restartReasons: string[];
hotReasons: string[];
reloadHooks: boolean;
restartGmailWatcher: boolean;
restartCron: boolean;
restartHeartbeat: boolean;
restartHealthMonitor: boolean;
restartChannels: Set<ChannelKind>;
noopPaths: string[];
};
type ReloadRule = {
prefix: string;
kind: "restart" | "hot" | "none";
actions?: ReloadAction[];
};
type ReloadAction =
| "reload-hooks"
| "restart-gmail-watcher"
| "restart-cron"
| "restart-heartbeat"
| "restart-health-monitor"
| `restart-channel:${ChannelId}`;
const BASE_RELOAD_RULES: ReloadRule[] = [
{ prefix: "gateway.remote", kind: "none" },
{ prefix: "gateway.reload", kind: "none" },
{
prefix: "gateway.channelHealthCheckMinutes",
kind: "hot",
actions: ["restart-health-monitor"],
},
{
prefix: "gateway.channelStaleEventThresholdMinutes",
kind: "hot",
actions: ["restart-health-monitor"],
},
{
prefix: "gateway.channelMaxRestartsPerHour",
kind: "hot",
actions: ["restart-health-monitor"],
},
// Stuck-session warning threshold is read by the diagnostics heartbeat loop.
{ prefix: "diagnostics.stuckSessionWarnMs", kind: "none" },
{ prefix: "hooks.gmail", kind: "hot", actions: ["restart-gmail-watcher"] },
{ prefix: "hooks", kind: "hot", actions: ["reload-hooks"] },
{
prefix: "agents.defaults.heartbeat",
kind: "hot",
actions: ["restart-heartbeat"],
},
{
prefix: "agents.defaults.models",
kind: "hot",
actions: ["restart-heartbeat"],
},
{
prefix: "agents.defaults.model",
kind: "hot",
actions: ["restart-heartbeat"],
},
{
prefix: "models",
kind: "hot",
actions: ["restart-heartbeat"],
},
{ prefix: "agent.heartbeat", kind: "hot", actions: ["restart-heartbeat"] },
{ prefix: "cron", kind: "hot", actions: ["restart-cron"] },
{ prefix: "browser", kind: "restart" },
];
const BASE_RELOAD_RULES_TAIL: ReloadRule[] = [
{ prefix: "meta", kind: "none" },
{ prefix: "identity", kind: "none" },
{ prefix: "wizard", kind: "none" },
{ prefix: "logging", kind: "none" },
{ prefix: "agents", kind: "none" },
{ prefix: "tools", kind: "none" },
{ prefix: "bindings", kind: "none" },
{ prefix: "audio", kind: "none" },
{ prefix: "agent", kind: "none" },
{ prefix: "routing", kind: "none" },
{ prefix: "messages", kind: "none" },
{ prefix: "session", kind: "none" },
{ prefix: "talk", kind: "none" },
{ prefix: "skills", kind: "none" },
{ prefix: "secrets", kind: "none" },
{ prefix: "plugins", kind: "restart" },
{ prefix: "ui", kind: "none" },
{ prefix: "gateway", kind: "restart" },
{ prefix: "discovery", kind: "restart" },
{ prefix: "canvasHost", kind: "restart" },
];
let cachedReloadRules: ReloadRule[] | null = null;
let cachedRegistry: ReturnType<typeof getActivePluginRegistry> | null = null;
function listReloadRules(): ReloadRule[] {
const registry = getActivePluginRegistry();
if (registry !== cachedRegistry) {
cachedReloadRules = null;
cachedRegistry = registry;
}
if (cachedReloadRules) {
return cachedReloadRules;
}
// Channel docking: plugins contribute hot reload/no-op prefixes here.
const channelReloadRules: ReloadRule[] = listChannelPlugins().flatMap((plugin) => [
...(plugin.reload?.configPrefixes ?? []).map(
(prefix): ReloadRule => ({
prefix,
kind: "hot",
actions: [`restart-channel:${plugin.id}` as ReloadAction],
}),
),
...(plugin.reload?.noopPrefixes ?? []).map(
(prefix): ReloadRule => ({
prefix,
kind: "none",
}),
),
]);
const rules = [...BASE_RELOAD_RULES, ...channelReloadRules, ...BASE_RELOAD_RULES_TAIL];
cachedReloadRules = rules;
return rules;
}
function matchRule(path: string): ReloadRule | null {
for (const rule of listReloadRules()) {
if (path === rule.prefix || path.startsWith(`${rule.prefix}.`)) {
return rule;
}
}
return null;
}
export function buildGatewayReloadPlan(changedPaths: string[]): GatewayReloadPlan {
const plan: GatewayReloadPlan = {
changedPaths,
restartGateway: false,
restartReasons: [],
hotReasons: [],
reloadHooks: false,
restartGmailWatcher: false,
restartCron: false,
restartHeartbeat: false,
restartHealthMonitor: false,
restartChannels: new Set(),
noopPaths: [],
};
const applyAction = (action: ReloadAction) => {
if (action.startsWith("restart-channel:")) {
const channel = action.slice("restart-channel:".length) as ChannelId;
plan.restartChannels.add(channel);
return;
}
switch (action) {
case "reload-hooks":
plan.reloadHooks = true;
break;
case "restart-gmail-watcher":
plan.restartGmailWatcher = true;
break;
case "restart-cron":
plan.restartCron = true;
break;
case "restart-heartbeat":
plan.restartHeartbeat = true;
break;
case "restart-health-monitor":
plan.restartHealthMonitor = true;
break;
default:
break;
}
};
for (const path of changedPaths) {
const rule = matchRule(path);
if (!rule) {
plan.restartGateway = true;
plan.restartReasons.push(path);
continue;
}
if (rule.kind === "restart") {
plan.restartGateway = true;
plan.restartReasons.push(path);
continue;
}
if (rule.kind === "none") {
plan.noopPaths.push(path);
continue;
}
plan.hotReasons.push(path);
for (const action of rule.actions ?? []) {
applyAction(action);
}
}
if (plan.restartGmailWatcher) {
plan.reloadHooks = true;
}
return plan;
}