mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
6192673da4
* refactor(plugin-sdk): delete the heavy runtime-doctor barrel
Nothing may pull the state-db/kysely graph through a doctor barrel anymore.
The barrel's remaining heavy exports move to two narrow private-local
subpaths, each with a single purpose:
- doctor-repair-runtime: install-path diagnosis, plugin config removal, and
state-database schema detect/repair (matrix doctor, voice-call lazy import)
- plugin-state-store-runtime: the sync keyed-store factory. It stays out of
plugin-state-runtime because hot channel entrypoints import that at module
load and opening a store pulls the state-database graph.
Doctor closures also stop pulling ssrf-runtime (fetch-guard + gateway net)
for two legacy private-network helpers that live in the lighter ssrf-policy
subpath: mattermost, nextcloud-talk, tlon, matrix.
The closure guard now forbids the two new heavy subpaths instead of the
deleted barrel, so the invariant keeps being enforced where it still applies.
* perf(doctor): keep heavy graphs out of every doctor closure
Doctor enumeration cold-loads each declaring plugin's contract closure, so
one heavy import in a closure is paid by the whole sweep. Four barrels were
still dragging unrelated graphs in for trivial helpers; each is repaired at
the leaf rather than by caching downstream:
- Legacy private-network config migration moves to a config leaf. It only
reshapes records, but lived beside the SSRF runtime (DNS, proxy, logging),
costing mattermost ~2.7s. ssrf-policy re-exports it, surface unchanged.
- Streaming config readers move to a leaf. They read two config keys, but
streaming.ts also formats tool aggregates, pulling tool-display/logging/
acp-core; that cost slack ~2.3s.
- signal took the channel-secret barrel for isRecord; the canonical plugin
record guard is string-coerce-runtime (root AGENTS.md).
- llm-task took the provider-model barrel for parseModelRef, now a narrow
model-ref-parse subpath.
Full doctor enumeration of all 42 declaring plugins, built mode:
legacy config rules 6668ms -> 1265ms, state migrations 184ms -> 127ms.
No plugin remains an outlier; the slowest is now ~380ms against a ~200ms floor.
Public export surfaces of every touched SDK subpath are byte-identical
(verified by diffing built module exports before/after); the API baseline
hashes move only because re-exported declarations emit differently.
The closure guard gains rules for each repaired barrel so the invariant
holds for future closures.
* fix(release): exclude new private-local declarations from the published package
Same pack-path rule as c41da3759f: private-local subpaths ship without d.ts.
* fix(doctor): repair the closure guard violations that break main
The landed guard fails on main: three closures import heavy barrels for one
symbol each. Two more surfaced once the guard learned about the provider-model
barrel. Each gets a narrow subpath at the leaf:
- telegram sent-message-cache + state-migrations took the session-store barrel
(session accessor + state-db) for resolveStorePath -> session-store-paths
- discord thread-bindings.state took the channel-outbound barrel (reply
pipeline + channel registry) for one identity write -> outbound-echo-runtime
- discord model-picker took the provider-model barrel for normalizeProviderId,
which model-ref-parse now exposes beside parseModelRef
The guard also stops walking artifacts of plugins whose manifest declares no
doctor surface. Such a declaration gates the artifact off every enumeration
path exactly as resolvePluginDoctorContracts does, so its closure cost is never
paid; anthropic ("doctorContract": {}) was being held to a cost it cannot
incur. Absent declarations still load eagerly and stay enforced.
Side effect worth naming: discord's built doctor contract now loads again.
On main both discord and telegram fail to require in packaged builds (an
ESM-only transitive dep) and silently lose their repairs; this restores
discord and takes enumerated legacy config rules from 87 to 99. Telegram's
built artifact still pulls execa through dist chunking - a build-level defect
with a different owner, filed as follow-up.
76 lines
2.7 KiB
TypeScript
76 lines
2.7 KiB
TypeScript
// Signal API module exposes the plugin doctor contract.
|
|
import type {
|
|
ChannelDoctorConfigMutation,
|
|
ChannelDoctorLegacyConfigRule,
|
|
} from "openclaw/plugin-sdk/channel-contract";
|
|
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
|
import { defineChannelAliasMigration } from "openclaw/plugin-sdk/runtime-doctor-migrations";
|
|
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
import { migrateLegacySignalTransportConfigSync } from "./src/config-compat.js";
|
|
|
|
const RETIRED_SIGNAL_ACCOUNT_TRANSPORT_FIELDS = [
|
|
"configPath",
|
|
"httpUrl",
|
|
"httpHost",
|
|
"httpPort",
|
|
"cliPath",
|
|
"autoStart",
|
|
"startupTimeoutMs",
|
|
"receiveMode",
|
|
"ignoreStories",
|
|
] as const;
|
|
|
|
function hasRetiredSignalAccountTransportFields(value: unknown): boolean {
|
|
return (
|
|
isRecord(value) &&
|
|
RETIRED_SIGNAL_ACCOUNT_TRANSPORT_FIELDS.some((field) => Object.hasOwn(value, field))
|
|
);
|
|
}
|
|
|
|
function hasRetiredSignalAccountMapTransportFields(value: unknown): boolean {
|
|
return isRecord(value) && Object.values(value).some(hasRetiredSignalAccountTransportFields);
|
|
}
|
|
|
|
// Signal's nested streaming schema is delivery-only ({chunkMode, block}); it
|
|
// has no preview mode, so only the delivery flat aliases are legal legacy
|
|
// input. Account merge replaces the root streaming object wholesale
|
|
// (resolveMergedAccountConfig without a streaming deep-merge), so migration
|
|
// seeds materialized account objects with the inherited root settings.
|
|
const streamingAliasMigration = defineChannelAliasMigration({
|
|
channelId: "signal",
|
|
streaming: { defaultMode: "partial", deliveryOnly: true },
|
|
accountStreamingReplacesRoot: true,
|
|
});
|
|
|
|
export const legacyConfigRules: ChannelDoctorLegacyConfigRule[] = [
|
|
...streamingAliasMigration.legacyConfigRules,
|
|
{
|
|
path: ["channels", "signal"],
|
|
message:
|
|
'Signal transport config is now account-owned; run "openclaw doctor --fix" to migrate retired channels.signal transport fields.',
|
|
match: (value) =>
|
|
isRecord(value) &&
|
|
(Object.hasOwn(value, "apiMode") || hasRetiredSignalAccountTransportFields(value)),
|
|
},
|
|
{
|
|
path: ["channels", "signal", "accounts"],
|
|
message:
|
|
'Signal transport config is now account-owned; run "openclaw doctor --fix" to migrate retired per-account transport fields.',
|
|
match: hasRetiredSignalAccountMapTransportFields,
|
|
},
|
|
];
|
|
|
|
export function normalizeCompatibilityConfig({
|
|
cfg,
|
|
}: {
|
|
cfg: OpenClawConfig;
|
|
}): ChannelDoctorConfigMutation {
|
|
const streaming = streamingAliasMigration.normalizeChannelConfig({ cfg });
|
|
const transport = migrateLegacySignalTransportConfigSync(streaming.config);
|
|
return {
|
|
config: transport.config,
|
|
changes: [...streaming.changes, ...transport.changes],
|
|
...(transport.warnings?.length ? { warnings: transport.warnings } : {}),
|
|
};
|
|
}
|