mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-23 19:21:18 +00:00
* fix(core): make indexed access explicit in auto-reply, infra, and config Part 1/3 of the src NUIA phase-3b burn-down (#104600): iteration and destructuring over index reads, boundary guards on parsed input, and named invariants. Config path walkers bind the path head once; SQLite migration key handling is hoisted without query-shape changes. * fix(core): make indexed access explicit in cli, gateway, commands, security, shared Part 2/3: argv/token selection restructured, gateway event/attachment invariants named, security parsers stay fail-closed (invariant violations throw), edit-distance matrices access checked entries. * fix(core): make indexed access explicit across remaining src surfaces Part 3/3: channels, plugins, process, cron, plugin-sdk, media, logging, tui, hooks, daemon, and small directories. Latent bug fixed: a tailnet resolver could leak undefined through a string|null contract and now fails with a descriptive local error. * fix(core): keep optional boundaries optional after per-commit review Review findings: expectDefined misused where absence is a legitimate state. CLI --profile/route-args missing next tokens take their existing miss paths; help normalization compares --help against the last positional again; first-time plugin install spreads absent cfg.plugins; denylist scan iterates manifest dependency entries instead of throwing on omitted sections; tailnet resolver returns a guaranteed string at the source instead of a caller-side undefined throw. * refactor(core): closed-key provider labels and honest optional passthroughs PROVIDER_LABELS becomes a satisfies-typed closed record (static reads provably defined; dynamic lookups go through providerUsageLabel with honest string|undefined). Status-scan overview passes its optional params through unchanged instead of asserting them. * fix(channels): make getChatChannelMeta honestly optional The original signature claimed ChatChannelMeta while leaking undefined on bundled channel id metadata drift; three of four callers already handled absence. The return type now says so, and the one assuming caller falls back to the raw channel label. * fix(core): index-safety for post-rebase main drift Covers the sqlite-sessions flip and auth-source-plan code that landed mid-phase, plus the channel-validation test consuming the now honestly optional getChatChannelMeta. * refactor(channels): split chat-meta accessors along the SDK contract getChatChannelMeta keeps its shipped plugin-SDK signature (defined for bundled ids, fail-loud on impossible misses); new findChatChannelMeta carries the drift-tolerant optional contract for core auto-enable and formatting paths. * fix(qa-channel): own channel metadata instead of a guaranteed-undefined catalog lookup qa-channel spread getChatChannelMeta over an id that is never in the bundled catalog, shipping an empty setup meta by accident; the fail-loud SDK accessor exposed it. The channel now declares its metadata once. * fix(gateway): heartbeat projection lookahead is optional at the transcript tail expectDefined wrapped messages[i + 1] whose absence on the final message is the normal case; the adjacent ternary already handled it. Restores the plain optional read with an explicit guard in the pair condition. * fix(plugin-sdk): channel plugin factory tolerates non-bundled channel ids again createChannelPluginBase spreads bundled catalog meta for ANY channel id, where absence is the normal case for external plugins; the resolver is honestly optional again while the exported bundled-id accessor keeps the fail-loud contract. * fix(core): spreads of optional config sections stay optional Fresh-setup and first-install paths (crestodian setup inference, hook installs, agent config base, target agent models) legitimately lack the section being rebuilt; spreading undefined is the shipped {} semantics. Removes the remaining gratuitous assertion wraps found by tree audit.
104 lines
2.8 KiB
TypeScript
104 lines
2.8 KiB
TypeScript
import { expectDefined } from "@openclaw/normalization-core";
|
|
// Provides root logger helpers and themed terminal output.
|
|
import { theme } from "../packages/terminal-core/src/theme.js";
|
|
import { isVerbose } from "./global-state.js";
|
|
import { getLogger } from "./logging/logger.js";
|
|
import { createSubsystemLogger } from "./logging/subsystem.js";
|
|
import { defaultRuntime, type RuntimeEnv } from "./runtime.js";
|
|
|
|
const subsystemPrefixRe = /^([a-z][a-z0-9-]{1,20}):\s+(.*)$/i;
|
|
|
|
function splitSubsystem(message: string) {
|
|
const match = message.match(subsystemPrefixRe);
|
|
if (!match) {
|
|
return null;
|
|
}
|
|
const subsystem = match.at(1);
|
|
const rest = match.at(2);
|
|
if (subsystem === undefined || rest === undefined) {
|
|
return null;
|
|
}
|
|
return { subsystem, rest };
|
|
}
|
|
|
|
type LogMethod = "info" | "warn" | "error";
|
|
type RuntimeMethod = "log" | "error";
|
|
|
|
function logWithSubsystem(params: {
|
|
message: string;
|
|
runtime: RuntimeEnv;
|
|
runtimeMethod: RuntimeMethod;
|
|
runtimeFormatter: (value: string) => string;
|
|
loggerMethod: LogMethod;
|
|
subsystemMethod: LogMethod;
|
|
}) {
|
|
const parsed = params.runtime === defaultRuntime ? splitSubsystem(params.message) : null;
|
|
if (parsed) {
|
|
const method = expectDefined(
|
|
createSubsystemLogger(parsed.subsystem)[params.subsystemMethod],
|
|
"subsystem logger method",
|
|
);
|
|
method(parsed.rest);
|
|
return;
|
|
}
|
|
params.runtime[params.runtimeMethod](params.runtimeFormatter(params.message));
|
|
getLogger()[params.loggerMethod](params.message);
|
|
}
|
|
|
|
const info = theme.info;
|
|
const warn = theme.warn;
|
|
const success = theme.success;
|
|
const danger = theme.error;
|
|
|
|
export function logInfo(message: string, runtime: RuntimeEnv = defaultRuntime) {
|
|
logWithSubsystem({
|
|
message,
|
|
runtime,
|
|
runtimeMethod: "log",
|
|
runtimeFormatter: info,
|
|
loggerMethod: "info",
|
|
subsystemMethod: "info",
|
|
});
|
|
}
|
|
|
|
export function logWarn(message: string, runtime: RuntimeEnv = defaultRuntime) {
|
|
logWithSubsystem({
|
|
message,
|
|
runtime,
|
|
runtimeMethod: "log",
|
|
runtimeFormatter: warn,
|
|
loggerMethod: "warn",
|
|
subsystemMethod: "warn",
|
|
});
|
|
}
|
|
|
|
export function logSuccess(message: string, runtime: RuntimeEnv = defaultRuntime) {
|
|
logWithSubsystem({
|
|
message,
|
|
runtime,
|
|
runtimeMethod: "log",
|
|
runtimeFormatter: success,
|
|
loggerMethod: "info",
|
|
subsystemMethod: "info",
|
|
});
|
|
}
|
|
|
|
export function logError(message: string, runtime: RuntimeEnv = defaultRuntime) {
|
|
logWithSubsystem({
|
|
message,
|
|
runtime,
|
|
runtimeMethod: "error",
|
|
runtimeFormatter: danger,
|
|
loggerMethod: "error",
|
|
subsystemMethod: "error",
|
|
});
|
|
}
|
|
|
|
export function logDebug(message: string) {
|
|
// Always emit to file logger (level-filtered); console only when verbose.
|
|
getLogger().debug(message);
|
|
if (isVerbose()) {
|
|
console.log(theme.muted(message));
|
|
}
|
|
}
|