mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 13:40:44 +00:00
fix: preserve formatted channel startup logs
This commit is contained in:
@@ -21,6 +21,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Gateway/logging: keep deferred channel startup logs on the subsystem logger, so Slack, Discord, Telegram, and voice-call startup messages keep timestamped prefixes. Thanks @vincentkoc.
|
||||
- Discord/reactions: skip reaction listener registration when DMs and group DMs are disabled and every configured guild has `reactionNotifications: "off"`, avoiding needless reaction-event queue work. Fixes #47516. Thanks @x4v13r1120.
|
||||
- CLI sessions: preserve explicit manual-attach reuse bindings so trusted CLI sessions are not invalidated on the first turn when auth, prompt, or MCP fingerprints drift. Fixes #75849. Thanks @alfredjbclaw.
|
||||
- Telegram/streaming: keep partial preview streaming enabled for plain reply-to replies, disabling drafts only for real native quote excerpts that require Telegram quote parameters. Fixes #73505. Thanks @choury.
|
||||
|
||||
@@ -307,6 +307,7 @@ export async function createVoiceCallRuntime(params: {
|
||||
coreConfig,
|
||||
fullConfig ?? (coreConfig as OpenClawConfig),
|
||||
agentRuntime,
|
||||
log,
|
||||
);
|
||||
if (realtimeProvider) {
|
||||
const { RealtimeCallHandler } = await loadRealtimeHandler();
|
||||
|
||||
@@ -35,6 +35,12 @@ const TRANSCRIPT_LOG_MAX_CHARS = 200;
|
||||
|
||||
type RealtimeTranscriptionRuntime = typeof import("./realtime-transcription.runtime.js");
|
||||
type ResponseGeneratorModule = typeof import("./response-generator.js");
|
||||
type Logger = {
|
||||
info: (message: string) => void;
|
||||
warn: (message: string) => void;
|
||||
error: (message: string) => void;
|
||||
debug?: (message: string) => void;
|
||||
};
|
||||
|
||||
let realtimeTranscriptionRuntimePromise: Promise<RealtimeTranscriptionRuntime> | undefined;
|
||||
let responseGeneratorModulePromise: Promise<ResponseGeneratorModule> | undefined;
|
||||
@@ -158,6 +164,7 @@ export class VoiceCallWebhookServer {
|
||||
private coreConfig: CoreConfig | null;
|
||||
private fullConfig: OpenClawConfig | null;
|
||||
private agentRuntime: CoreAgentDeps | null;
|
||||
private logger: Logger;
|
||||
private stopStaleCallReaper: (() => void) | null = null;
|
||||
private readonly webhookInFlightLimiter = createWebhookInFlightLimiter();
|
||||
|
||||
@@ -175,6 +182,7 @@ export class VoiceCallWebhookServer {
|
||||
coreConfig?: CoreConfig,
|
||||
fullConfig?: OpenClawConfig,
|
||||
agentRuntime?: CoreAgentDeps,
|
||||
logger?: Logger,
|
||||
) {
|
||||
this.config = normalizeVoiceCallConfig(config);
|
||||
this.manager = manager;
|
||||
@@ -182,6 +190,12 @@ export class VoiceCallWebhookServer {
|
||||
this.coreConfig = coreConfig ?? null;
|
||||
this.fullConfig = fullConfig ?? null;
|
||||
this.agentRuntime = agentRuntime ?? null;
|
||||
this.logger = logger ?? {
|
||||
info: console.log,
|
||||
warn: console.warn,
|
||||
error: console.error,
|
||||
debug: console.debug,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -492,12 +506,12 @@ export class VoiceCallWebhookServer {
|
||||
const url = this.resolveListeningUrl(bind, webhookPath);
|
||||
this.listeningUrl = url;
|
||||
this.startPromise = null;
|
||||
console.log(`[voice-call] Webhook server listening on ${url}`);
|
||||
this.logger.info(`[voice-call] Webhook server listening on ${url}`);
|
||||
if (this.mediaStreamHandler) {
|
||||
const address = this.server?.address();
|
||||
const actualPort =
|
||||
address && typeof address === "object" ? address.port : this.config.serve.port;
|
||||
console.log(
|
||||
this.logger.info(
|
||||
`[voice-call] Media stream WebSocket on ws://${bind}:${actualPort}${streamPath}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -354,6 +354,26 @@ describe("server-channels auto restart", () => {
|
||||
expect(ctx?.channelRuntime).not.toBe(channelRuntime);
|
||||
});
|
||||
|
||||
it("creates formatted runtime and log sinks for channels loaded after manager construction", async () => {
|
||||
const startAccount = vi.fn(async (_ctx: ChannelGatewayContext<TestAccount>) => {});
|
||||
installTestRegistry(createTestPlugin({ id: "slack", startAccount }));
|
||||
const channelLogs = {} as Record<ChannelId, SubsystemLogger>;
|
||||
const channelRuntimeEnvs = {} as Record<ChannelId, RuntimeEnv>;
|
||||
const manager = createChannelManager({
|
||||
getRuntimeConfig: () => ({}),
|
||||
channelLogs,
|
||||
channelRuntimeEnvs,
|
||||
});
|
||||
|
||||
await manager.startChannel("slack");
|
||||
|
||||
expect(startAccount).toHaveBeenCalledTimes(1);
|
||||
const [ctx] = startAccount.mock.calls[0] ?? [];
|
||||
expect(ctx?.log).toBe(channelLogs.slack);
|
||||
expect(ctx?.runtime).toBe(channelRuntimeEnvs.slack);
|
||||
expect((ctx?.log as SubsystemLogger | undefined)?.subsystem).toBe("channels/slack");
|
||||
});
|
||||
|
||||
it("deduplicates concurrent start requests for the same account", async () => {
|
||||
const startupGate = createDeferred();
|
||||
const isConfigured = vi.fn(async () => {
|
||||
|
||||
@@ -13,7 +13,7 @@ import { type BackoffPolicy, computeBackoff, sleepWithAbort } from "../infra/bac
|
||||
import { createTaskScopedChannelRuntime } from "../infra/channel-runtime-context.js";
|
||||
import { formatErrorMessage } from "../infra/errors.js";
|
||||
import { resetDirectoryCache } from "../infra/outbound/target-resolver.js";
|
||||
import type { createSubsystemLogger } from "../logging/subsystem.js";
|
||||
import { createSubsystemLogger, runtimeForLogger } from "../logging/subsystem.js";
|
||||
import { resolveAccountEntry, resolveNormalizedAccountEntry } from "../routing/account-lookup.js";
|
||||
import {
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
@@ -214,6 +214,14 @@ export function createChannelManager(opts: ChannelManagerOptions): ChannelManage
|
||||
const manuallyStopped = new Set<string>();
|
||||
|
||||
const restartKey = (channelId: ChannelId, accountId: string) => `${channelId}:${accountId}`;
|
||||
const ensureChannelLog = (channelId: ChannelId): SubsystemLogger => {
|
||||
channelLogs[channelId] ??= createSubsystemLogger("channels").child(channelId);
|
||||
return channelLogs[channelId];
|
||||
};
|
||||
const ensureChannelRuntime = (channelId: ChannelId): RuntimeEnv => {
|
||||
channelRuntimeEnvs[channelId] ??= runtimeForLogger(ensureChannelLog(channelId));
|
||||
return channelRuntimeEnvs[channelId];
|
||||
};
|
||||
|
||||
const resolveAccountHealthMonitorOverride = (
|
||||
channelConfig: ChannelHealthMonitorConfig | undefined,
|
||||
@@ -265,7 +273,7 @@ export function createChannelManager(opts: ChannelManagerOptions): ChannelManage
|
||||
// This call exists solely to fail closed if resolver-side config loading is broken.
|
||||
plugin.config.resolveAccount(cfg, accountId);
|
||||
} catch (err) {
|
||||
channelLogs[channelId].warn?.(
|
||||
ensureChannelLog(channelId).warn?.(
|
||||
`[${channelId}:${accountId}] health-monitor: failed to resolve account; skipping monitor (${formatErrorMessage(err)})`,
|
||||
);
|
||||
return false;
|
||||
@@ -388,7 +396,8 @@ export function createChannelManager(opts: ChannelManagerOptions): ChannelManage
|
||||
const abort = new AbortController();
|
||||
store.aborts.set(id, abort);
|
||||
let handedOffTask = false;
|
||||
const log = channelLogs[channelId];
|
||||
const log = ensureChannelLog(channelId);
|
||||
const runtime = ensureChannelRuntime(channelId);
|
||||
let scopedChannelRuntime: ReturnType<typeof createTaskScopedChannelRuntime> | null = null;
|
||||
let channelRuntimeForTask: ChannelRuntimeSurface | undefined;
|
||||
let stopApprovalBootstrap: () => Promise<void> = async () => {};
|
||||
@@ -499,7 +508,7 @@ export function createChannelManager(opts: ChannelManagerOptions): ChannelManage
|
||||
cfg,
|
||||
accountId: id,
|
||||
account,
|
||||
runtime: channelRuntimeEnvs[channelId],
|
||||
runtime,
|
||||
abortSignal: abort.signal,
|
||||
log,
|
||||
getStatus: () => getRuntime(channelId, id),
|
||||
@@ -641,16 +650,17 @@ export function createChannelManager(opts: ChannelManagerOptions): ChannelManage
|
||||
}
|
||||
manuallyStopped.add(restartKey(channelId, id));
|
||||
abort?.abort();
|
||||
const log = channelLogs[channelId];
|
||||
const log = ensureChannelLog(channelId);
|
||||
const runtime = ensureChannelRuntime(channelId);
|
||||
if (plugin?.gateway?.stopAccount) {
|
||||
const account = plugin.config.resolveAccount(cfg, id);
|
||||
await plugin.gateway.stopAccount({
|
||||
cfg,
|
||||
accountId: id,
|
||||
account,
|
||||
runtime: channelRuntimeEnvs[channelId],
|
||||
runtime,
|
||||
abortSignal: abort?.signal ?? new AbortController().signal,
|
||||
log: channelLogs[channelId],
|
||||
log,
|
||||
getStatus: () => getRuntime(channelId, id),
|
||||
setStatus: (next) => setRuntime(channelId, id, next),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user