Spracherkennung für: .ts vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]
import type { OpenClawConfig } from "../config/types.openclaw.js";
import type { DiagnosticTraceContext } from "./diagnostic-trace-context.js";
export type DiagnosticSessionState = "idle" | "processing" | "waiting";
type DiagnosticBaseEvent = {
ts: number;
seq: number;
trace?: DiagnosticTraceContext;
};
export type DiagnosticUsageEvent = DiagnosticBaseEvent & {
type: "model.usage";
sessionKey?: string;
sessionId?: string;
channel?: string;
provider?: string;
model?: string;
usage: {
input?: number;
output?: number;
cacheRead?: number;
cacheWrite?: number;
promptTokens?: number;
total?: number;
};
lastCallUsage?: {
input?: number;
output?: number;
cacheRead?: number;
cacheWrite?: number;
total?: number;
};
context?: {
limit?: number;
used?: number;
};
costUsd?: number;
durationMs?: number;
};
export type DiagnosticWebhookReceivedEvent = DiagnosticBaseEvent & {
type: "webhook.received";
channel: string;
updateType?: string;
chatId?: number | string;
};
export type DiagnosticWebhookProcessedEvent = DiagnosticBaseEvent & {
type: "webhook.processed";
channel: string;
updateType?: string;
chatId?: number | string;
durationMs?: number;
};
export type DiagnosticWebhookErrorEvent = DiagnosticBaseEvent & {
type: "webhook.error";
channel: string;
updateType?: string;
chatId?: number | string;
error: string;
};
export type DiagnosticMessageQueuedEvent = DiagnosticBaseEvent & {
type: "message.queued";
sessionKey?: string;
sessionId?: string;
channel?: string;
source: string;
queueDepth?: number;
};
export type DiagnosticMessageProcessedEvent = DiagnosticBaseEvent & {
type: "message.processed";
channel: string;
messageId?: number | string;
chatId?: number | string;
sessionKey?: string;
sessionId?: string;
durationMs?: number;
outcome: "completed" | "skipped" | "error";
reason?: string;
error?: string;
};
export type DiagnosticSessionStateEvent = DiagnosticBaseEvent & {
type: "session.state";
sessionKey?: string;
sessionId?: string;
prevState?: DiagnosticSessionState;
state: DiagnosticSessionState;
reason?: string;
queueDepth?: number;
};
export type DiagnosticSessionStuckEvent = DiagnosticBaseEvent & {
type: "session.stuck";
sessionKey?: string;
sessionId?: string;
state: DiagnosticSessionState;
ageMs: number;
queueDepth?: number;
};
export type DiagnosticLaneEnqueueEvent = DiagnosticBaseEvent & {
type: "queue.lane.enqueue";
lane: string;
queueSize: number;
};
export type DiagnosticLaneDequeueEvent = DiagnosticBaseEvent & {
type: "queue.lane.dequeue";
lane: string;
queueSize: number;
waitMs: number;
};
export type DiagnosticRunAttemptEvent = DiagnosticBaseEvent & {
type: "run.attempt";
sessionKey?: string;
sessionId?: string;
runId: string;
attempt: number;
};
export type DiagnosticHeartbeatEvent = DiagnosticBaseEvent & {
type: "diagnostic.heartbeat";
webhooks: {
received: number;
processed: number;
errors: number;
};
active: number;
waiting: number;
queued: number;
};
export type DiagnosticToolLoopEvent = DiagnosticBaseEvent & {
type: "tool.loop";
sessionKey?: string;
sessionId?: string;
toolName: string;
level: "warning" | "critical";
action: "warn" | "block";
detector:
| "generic_repeat"
| "unknown_tool_repeat"
| "known_poll_no_progress"
| "global_circuit_breaker"
| "ping_pong";
count: number;
message: string;
pairedToolName?: string;
};
export type DiagnosticToolParamsSummary =
| { kind: "object" }
| { kind: "array"; length: number }
| { kind: "string"; length: number }
| { kind: "number" | "boolean" | "null" | "undefined" | "other" };
type DiagnosticToolExecutionBaseEvent = DiagnosticBaseEvent & {
runId?: string;
sessionKey?: string;
sessionId?: string;
toolName: string;
toolCallId?: string;
paramsSummary?: DiagnosticToolParamsSummary;
};
export type DiagnosticToolExecutionStartedEvent = DiagnosticToolExecutionBaseEvent & {
type: "tool.execution.started";
};
export type DiagnosticToolExecutionCompletedEvent = DiagnosticToolExecutionBaseEvent & {
type: "tool.execution.completed";
durationMs: number;
};
export type DiagnosticToolExecutionErrorEvent = DiagnosticToolExecutionBaseEvent & {
type: "tool.execution.error";
durationMs: number;
errorCategory: string;
errorCode?: string;
};
export type DiagnosticExecProcessCompletedEvent = DiagnosticBaseEvent & {
type: "exec.process.completed";
sessionKey?: string;
target: "host" | "sandbox";
mode: "child" | "pty";
outcome: "completed" | "failed";
durationMs: number;
commandLength: number;
exitCode?: number;
exitSignal?: string;
timedOut?: boolean;
failureKind?:
| "shell-command-not-found"
| "shell-not-executable"
| "overall-timeout"
| "no-output-timeout"
| "signal"
| "aborted"
| "runtime-error";
};
type DiagnosticRunBaseEvent = DiagnosticBaseEvent & {
runId: string;
sessionKey?: string;
sessionId?: string;
provider?: string;
model?: string;
trigger?: string;
channel?: string;
};
export type DiagnosticRunStartedEvent = DiagnosticRunBaseEvent & {
type: "run.started";
};
export type DiagnosticRunCompletedEvent = DiagnosticRunBaseEvent & {
type: "run.completed";
durationMs: number;
outcome: "completed" | "aborted" | "error";
errorCategory?: string;
};
type DiagnosticModelCallBaseEvent = DiagnosticBaseEvent & {
type: "model.call.started" | "model.call.completed" | "model.call.error";
runId: string;
callId: string;
sessionKey?: string;
sessionId?: string;
provider: string;
model: string;
api?: string;
transport?: string;
};
export type DiagnosticModelCallStartedEvent = DiagnosticModelCallBaseEvent & {
type: "model.call.started";
};
export type DiagnosticModelCallCompletedEvent = DiagnosticModelCallBaseEvent & {
type: "model.call.completed";
durationMs: number;
};
export type DiagnosticModelCallErrorEvent = DiagnosticModelCallBaseEvent & {
type: "model.call.error";
durationMs: number;
errorCategory: string;
};
export type DiagnosticMemoryUsage = {
rssBytes: number;
heapTotalBytes: number;
heapUsedBytes: number;
externalBytes: number;
arrayBuffersBytes: number;
};
export type DiagnosticMemorySampleEvent = DiagnosticBaseEvent & {
type: "diagnostic.memory.sample";
memory: DiagnosticMemoryUsage;
uptimeMs?: number;
};
export type DiagnosticMemoryPressureEvent = DiagnosticBaseEvent & {
type: "diagnostic.memory.pressure";
level: "warning" | "critical";
reason: "rss_threshold" | "heap_threshold" | "rss_growth";
memory: DiagnosticMemoryUsage;
thresholdBytes?: number;
rssGrowthBytes?: number;
windowMs?: number;
};
export type DiagnosticPayloadLargeEvent = DiagnosticBaseEvent & {
type: "payload.large";
surface: string;
action: "rejected" | "truncated" | "chunked";
bytes?: number;
limitBytes?: number;
count?: number;
channel?: string;
pluginId?: string;
reason?: string;
};
export type DiagnosticLogRecordEvent = DiagnosticBaseEvent & {
type: "log.record";
level: string;
message: string;
loggerName?: string;
loggerParents?: string[];
attributes?: Record<string, string | number | boolean>;
code?: {
line?: number;
functionName?: string;
};
};
export type DiagnosticEventPayload =
| DiagnosticUsageEvent
| DiagnosticWebhookReceivedEvent
| DiagnosticWebhookProcessedEvent
| DiagnosticWebhookErrorEvent
| DiagnosticMessageQueuedEvent
| DiagnosticMessageProcessedEvent
| DiagnosticSessionStateEvent
| DiagnosticSessionStuckEvent
| DiagnosticLaneEnqueueEvent
| DiagnosticLaneDequeueEvent
| DiagnosticRunAttemptEvent
| DiagnosticHeartbeatEvent
| DiagnosticToolLoopEvent
| DiagnosticToolExecutionStartedEvent
| DiagnosticToolExecutionCompletedEvent
| DiagnosticToolExecutionErrorEvent
| DiagnosticExecProcessCompletedEvent
| DiagnosticRunStartedEvent
| DiagnosticRunCompletedEvent
| DiagnosticModelCallStartedEvent
| DiagnosticModelCallCompletedEvent
| DiagnosticModelCallErrorEvent
| DiagnosticMemorySampleEvent
| DiagnosticMemoryPressureEvent
| DiagnosticPayloadLargeEvent
| DiagnosticLogRecordEvent;
export type DiagnosticEventInput = DiagnosticEventPayload extends infer Event
? Event extends DiagnosticEventPayload
? Omit<Event, "seq" | "ts">
: never
: never;
type DiagnosticEventsGlobalState = {
enabled: boolean;
seq: number;
listeners: Set<(evt: DiagnosticEventPayload) => void>;
dispatchDepth: number;
asyncQueue: DiagnosticEventPayload[];
asyncDrainScheduled: boolean;
};
const MAX_ASYNC_DIAGNOSTIC_EVENTS = 10_000;
const ASYNC_DIAGNOSTIC_EVENT_TYPES = new Set<DiagnosticEventPayload["type"]>([
"tool.execution.started",
"tool.execution.completed",
"tool.execution.error",
"exec.process.completed",
"model.call.started",
"model.call.completed",
"model.call.error",
"log.record",
]);
function getDiagnosticEventsState(): DiagnosticEventsGlobalState {
const globalStore = globalThis as typeof globalThis & {
__openclawDiagnosticEventsState?: DiagnosticEventsGlobalState;
};
if (!globalStore.__openclawDiagnosticEventsState) {
globalStore.__openclawDiagnosticEventsState = {
enabled: true,
seq: 0,
listeners: new Set<(evt: DiagnosticEventPayload) => void>(),
dispatchDepth: 0,
asyncQueue: [],
asyncDrainScheduled: false,
};
}
return globalStore.__openclawDiagnosticEventsState;
}
export function isDiagnosticsEnabled(config?: OpenClawConfig): boolean {
return config?.diagnostics?.enabled !== false;
}
export function setDiagnosticsEnabledForProcess(enabled: boolean): void {
getDiagnosticEventsState().enabled = enabled;
}
export function areDiagnosticsEnabledForProcess(): boolean {
return getDiagnosticEventsState().enabled;
}
function dispatchDiagnosticEvent(
state: DiagnosticEventsGlobalState,
enriched: DiagnosticEventPayload,
): void {
if (state.dispatchDepth > 100) {
console.error(
`[diagnostic-events] recursion guard tripped at depth=${state.dispatchDepth}, dropping type=${enriched.type}`,
);
return;
}
state.dispatchDepth += 1;
try {
for (const listener of state.listeners) {
try {
listener(enriched);
} catch (err) {
const errorMessage =
err instanceof Error
? (err.stack ?? err.message)
: typeof err === "string"
? err
: String(err);
console.error(
`[diagnostic-events] listener error type=${enriched.type} seq=${enriched.seq}: ${errorMessage}`,
);
// Ignore listener failures.
}
}
} finally {
state.dispatchDepth -= 1;
}
}
function scheduleAsyncDiagnosticDrain(state: DiagnosticEventsGlobalState): void {
if (state.asyncDrainScheduled) {
return;
}
state.asyncDrainScheduled = true;
setImmediate(() => {
state.asyncDrainScheduled = false;
const batch = state.asyncQueue.splice(0);
for (const event of batch) {
dispatchDiagnosticEvent(state, event);
}
if (state.asyncQueue.length > 0) {
scheduleAsyncDiagnosticDrain(state);
}
});
}
export function emitDiagnosticEvent(event: DiagnosticEventInput) {
const state = getDiagnosticEventsState();
if (!state.enabled) {
return;
}
const enriched = {
...event,
seq: (state.seq += 1),
ts: Date.now(),
} satisfies DiagnosticEventPayload;
if (ASYNC_DIAGNOSTIC_EVENT_TYPES.has(enriched.type)) {
if (state.asyncQueue.length >= MAX_ASYNC_DIAGNOSTIC_EVENTS) {
return;
}
state.asyncQueue.push(enriched);
scheduleAsyncDiagnosticDrain(state);
return;
}
dispatchDiagnosticEvent(state, enriched);
}
export function onInternalDiagnosticEvent(
listener: (evt: DiagnosticEventPayload) => void,
): () => void {
const state = getDiagnosticEventsState();
state.listeners.add(listener);
return () => {
state.listeners.delete(listener);
};
}
export function onDiagnosticEvent(listener: (evt: DiagnosticEventPayload) => void): () => void {
return onInternalDiagnosticEvent((event) => {
if (event.type === "log.record") {
return;
}
listener(event);
});
}
export function resetDiagnosticEventsForTest(): void {
const state = getDiagnosticEventsState();
state.enabled = true;
state.seq = 0;
state.listeners.clear();
state.dispatchDepth = 0;
state.asyncQueue = [];
state.asyncDrainScheduled = false;
}
¤ Dauer der Verarbeitung: 0.19 Sekunden
(vorverarbeitet am 2026-04-27)
¤
*© Formatika GbR, Deutschland