feat(diagnostics): carry trace context through hooks

Pass immutable diagnostic trace contexts through agent and tool hook surfaces, emit model usage with the run trace, and parent OTEL spans/logs from validated trace context without retained global state.\n\nThanks @vincentkoc.
This commit is contained in:
Vincent Koc
2026-04-24 00:24:32 -07:00
committed by GitHub
parent 33c0cd1378
commit bcdacfa1b3
18 changed files with 229 additions and 35 deletions

View File

@@ -8,6 +8,7 @@ Docs: https://docs.openclaw.ai
- Diagnostics/OTEL: add a lightweight diagnostic trace-context carrier for future span correlation without adding OTEL SDK state to core. Thanks @vincentkoc.
- Diagnostics/OTEL: attach diagnostic trace context to exported OTEL logs so log records can correlate with future spans without adding retained process state. Thanks @vincentkoc.
- Diagnostics/OTEL: pass immutable per-run diagnostic trace context through agent and tool hook contexts, and parent exported diagnostic spans from validated context without retaining global trace state. Thanks @vincentkoc.
- Control UI/chat: add a Steer action on queued messages so a browser follow-up can be injected into the active run without retyping it.
- Control UI/Talk: add browser WebRTC realtime voice sessions backed by OpenAI Realtime, with Gateway-minted ephemeral client secrets and `openclaw_agent_consult` handoff to the full OpenClaw agent.
- Agents/tools: add optional per-call `timeoutMs` support for image, video, music, and TTS generation tools so agents can extend provider request timeouts only when a specific generation needs it.

View File

@@ -1,2 +1,2 @@
ad7ec565b1702a76a87b1a08904445c9838e10d4d41fb1c58909af886b702d80 plugin-sdk-api-baseline.json
907a07c206dd52ebd910793fab7bca8640c37cf82ff7e7cca88ab1b12b4fbdfe plugin-sdk-api-baseline.jsonl
c0f788d1895ced2ffdad9f82e6afc592171e6651c61c0fc5083f0040437cda6d plugin-sdk-api-baseline.json
70e320157331080b98f9c2acae58e89ad1dc70b48adad265225a7eb76b6ac29f plugin-sdk-api-baseline.jsonl

View File

@@ -106,7 +106,7 @@ const handler = async (event) => {
export default handler;
```
Each event includes: `type`, `action`, `sessionKey`, `timestamp`, `messages` (push to send to user), and `context` (event-specific data).
Each event includes: `type`, `action`, `sessionKey`, `timestamp`, `messages` (push to send to user), and `context` (event-specific data). Agent and tool plugin hook contexts can also include `trace`, a read-only W3C-compatible diagnostic trace context that plugins may pass into structured logs for OTEL correlation.
### Event context highlights

View File

@@ -6,7 +6,7 @@ const telemetryState = vi.hoisted(() => {
const counters = new Map<string, { add: ReturnType<typeof vi.fn> }>();
const histograms = new Map<string, { record: ReturnType<typeof vi.fn> }>();
const tracer = {
startSpan: vi.fn((_name: string, _opts?: unknown) => ({
startSpan: vi.fn((_name: string, _opts?: unknown, _ctx?: unknown) => ({
end: vi.fn(),
setStatus: vi.fn(),
})),
@@ -384,6 +384,64 @@ describe("diagnostics-otel service", () => {
});
});
test("parents diagnostic event spans from trace context", async () => {
const service = createDiagnosticsOtelService();
const ctx = createOtelContext(OTEL_TEST_ENDPOINT, { traces: true, metrics: true });
await service.start(ctx);
emitDiagnosticEvent({
type: "model.usage",
trace: {
traceId: TRACE_ID,
spanId: SPAN_ID,
traceFlags: "01",
},
provider: "openai",
model: "gpt-5.4",
usage: { total: 4 },
durationMs: 12,
});
const modelUsageCall = telemetryState.tracer.startSpan.mock.calls.find(
(call) => call[0] === "openclaw.model.usage",
);
expect(modelUsageCall?.[2]).toEqual({
spanContext: expect.objectContaining({
traceId: TRACE_ID,
spanId: SPAN_ID,
traceFlags: 1,
isRemote: true,
}),
});
await service.stop?.(ctx);
});
test("ignores invalid diagnostic event trace parents", async () => {
const service = createDiagnosticsOtelService();
const ctx = createOtelContext(OTEL_TEST_ENDPOINT, { traces: true, metrics: true });
await service.start(ctx);
emitDiagnosticEvent({
type: "model.usage",
trace: {
traceId: "0".repeat(32),
spanId: "not-a-span",
traceFlags: "zz",
},
provider: "openai",
model: "gpt-5.4",
usage: { total: 4 },
durationMs: 12,
});
const modelUsageCall = telemetryState.tracer.startSpan.mock.calls.find(
(call) => call[0] === "openclaw.model.usage",
);
expect(telemetryState.tracer.setSpanContext).not.toHaveBeenCalled();
expect(modelUsageCall?.[2]).toBeUndefined();
await service.stop?.(ctx);
});
test("redacts sensitive reason in session.state metric attributes", async () => {
const service = createDiagnosticsOtelService();
const ctx = createOtelContext(OTEL_TEST_ENDPOINT, { metrics: true });

View File

@@ -137,22 +137,36 @@ function traceFlagsToOtel(traceFlags: string | undefined): TraceFlags {
return (parsed & TraceFlags.SAMPLED) !== 0 ? TraceFlags.SAMPLED : TraceFlags.NONE;
}
function contextForTraceContext(traceContext: DiagnosticTraceContext | undefined) {
const normalized = normalizeTraceContext(traceContext);
if (!normalized?.spanId) {
return undefined;
}
return trace.setSpanContext(otelContextApi.active(), {
traceId: normalized.traceId,
spanId: normalized.spanId,
traceFlags: traceFlagsToOtel(normalized.traceFlags),
isRemote: true,
});
}
function addTraceAttributes(
attributes: Record<string, string | number | boolean>,
traceContext: DiagnosticTraceContext | undefined,
): void {
if (!traceContext) {
const normalized = normalizeTraceContext(traceContext);
if (!normalized) {
return;
}
attributes["openclaw.traceId"] = traceContext.traceId;
if (traceContext.spanId) {
attributes["openclaw.spanId"] = traceContext.spanId;
attributes["openclaw.traceId"] = normalized.traceId;
if (normalized.spanId) {
attributes["openclaw.spanId"] = normalized.spanId;
}
if (traceContext.parentSpanId) {
attributes["openclaw.parentSpanId"] = traceContext.parentSpanId;
if (normalized.parentSpanId) {
attributes["openclaw.parentSpanId"] = normalized.parentSpanId;
}
if (traceContext.traceFlags) {
attributes["openclaw.traceFlags"] = traceContext.traceFlags;
if (normalized.traceFlags) {
attributes["openclaw.traceFlags"] = normalized.traceFlags;
}
}
@@ -448,13 +462,9 @@ export function createDiagnosticsOtelService(): OpenClawPluginService {
attributes: redactOtelAttributes(attributes),
timestamp: meta?.date ?? new Date(),
};
if (traceContext?.spanId) {
logRecord.context = trace.setSpanContext(otelContextApi.active(), {
traceId: traceContext.traceId,
spanId: traceContext.spanId,
traceFlags: traceFlagsToOtel(traceContext.traceFlags),
isRemote: true,
});
const logContext = contextForTraceContext(traceContext);
if (logContext) {
logRecord.context = logContext;
}
otelLogger.emit(logRecord);
} catch (err) {
@@ -467,13 +477,19 @@ export function createDiagnosticsOtelService(): OpenClawPluginService {
name: string,
attributes: Record<string, string | number>,
durationMs?: number,
traceContext?: DiagnosticTraceContext,
) => {
const startTime =
typeof durationMs === "number" ? Date.now() - Math.max(0, durationMs) : undefined;
const span = tracer.startSpan(name, {
attributes,
...(startTime ? { startTime } : {}),
});
const parentContext = contextForTraceContext(traceContext);
const span = tracer.startSpan(
name,
{
attributes,
...(startTime ? { startTime } : {}),
},
parentContext,
);
return span;
};
@@ -537,7 +553,7 @@ export function createDiagnosticsOtelService(): OpenClawPluginService {
"openclaw.tokens.total": usage.total ?? 0,
};
const span = spanWithDuration("openclaw.model.usage", spanAttrs, evt.durationMs);
const span = spanWithDuration("openclaw.model.usage", spanAttrs, evt.durationMs, evt.trace);
span.end();
};
@@ -568,7 +584,12 @@ export function createDiagnosticsOtelService(): OpenClawPluginService {
if (evt.chatId !== undefined) {
spanAttrs["openclaw.chatId"] = String(evt.chatId);
}
const span = spanWithDuration("openclaw.webhook.processed", spanAttrs, evt.durationMs);
const span = spanWithDuration(
"openclaw.webhook.processed",
spanAttrs,
evt.durationMs,
evt.trace,
);
span.end();
};
@@ -591,9 +612,13 @@ export function createDiagnosticsOtelService(): OpenClawPluginService {
if (evt.chatId !== undefined) {
spanAttrs["openclaw.chatId"] = String(evt.chatId);
}
const span = tracer.startSpan("openclaw.webhook.error", {
attributes: spanAttrs,
});
const span = tracer.startSpan(
"openclaw.webhook.error",
{
attributes: spanAttrs,
},
contextForTraceContext(evt.trace),
);
span.setStatus({ code: SpanStatusCode.ERROR, message: redactedError });
span.end();
};
@@ -648,7 +673,12 @@ export function createDiagnosticsOtelService(): OpenClawPluginService {
if (evt.reason) {
spanAttrs["openclaw.reason"] = redactSensitiveText(evt.reason);
}
const span = spanWithDuration("openclaw.message.processed", spanAttrs, evt.durationMs);
const span = spanWithDuration(
"openclaw.message.processed",
spanAttrs,
evt.durationMs,
evt.trace,
);
if (evt.outcome === "error" && evt.error) {
span.setStatus({ code: SpanStatusCode.ERROR, message: redactSensitiveText(evt.error) });
}
@@ -699,7 +729,11 @@ export function createDiagnosticsOtelService(): OpenClawPluginService {
addSessionIdentityAttrs(spanAttrs, evt);
spanAttrs["openclaw.queueDepth"] = evt.queueDepth ?? 0;
spanAttrs["openclaw.ageMs"] = evt.ageMs;
const span = tracer.startSpan("openclaw.session.stuck", { attributes: spanAttrs });
const span = tracer.startSpan(
"openclaw.session.stuck",
{ attributes: spanAttrs },
contextForTraceContext(evt.trace),
);
span.setStatus({ code: SpanStatusCode.ERROR, message: "session stuck" });
span.end();
};

View File

@@ -7,6 +7,7 @@ import { ensureContextEnginesInitialized } from "../../context-engine/init.js";
import { resolveContextEngine } from "../../context-engine/registry.js";
import { emitAgentPlanEvent } from "../../infra/agent-events.js";
import { sleepWithAbort } from "../../infra/backoff.js";
import { freezeDiagnosticTraceContext } from "../../infra/diagnostic-trace-context.js";
import { formatErrorMessage } from "../../infra/errors.js";
import { getGlobalHookRunner } from "../../plugins/hook-runner-global.js";
import { enqueueCommandInLane } from "../../process/command-queue.js";
@@ -2134,6 +2135,9 @@ export async function runEmbeddedPiAgent(
});
return {
payloads: payloadsWithToolMedia?.length ? payloadsWithToolMedia : undefined,
...(attempt.diagnosticTrace
? { diagnosticTrace: freezeDiagnosticTraceContext(attempt.diagnosticTrace) }
: {}),
meta: {
durationMs: Date.now() - started,
agentMeta,

View File

@@ -1,14 +1,21 @@
import {
freezeDiagnosticTraceContext,
type DiagnosticTraceContext,
} from "../../../infra/diagnostic-trace-context.js";
import type { EmbeddedRunTrigger } from "./params.js";
export function buildEmbeddedAttemptToolRunContext(params: {
trigger?: EmbeddedRunTrigger;
memoryFlushWritePath?: string;
trace?: DiagnosticTraceContext;
}): {
trigger?: EmbeddedRunTrigger;
memoryFlushWritePath?: string;
trace?: DiagnosticTraceContext;
} {
return {
trigger: params.trigger,
memoryFlushWritePath: params.memoryFlushWritePath,
...(params.trace ? { trace: freezeDiagnosticTraceContext(params.trace) } : {}),
};
}

View File

@@ -9,6 +9,10 @@ import {
} from "@mariozechner/pi-coding-agent";
import { filterHeartbeatPairs } from "../../../auto-reply/heartbeat-filter.js";
import { resolveChannelCapabilities } from "../../../config/channel-capabilities.js";
import {
createDiagnosticTraceContext,
freezeDiagnosticTraceContext,
} from "../../../infra/diagnostic-trace-context.js";
import { isEmbeddedMode } from "../../../infra/embedded-mode.js";
import { formatErrorMessage } from "../../../infra/errors.js";
import { resolveHeartbeatSummaryForAgent } from "../../../infra/heartbeat-summary.js";
@@ -504,12 +508,13 @@ export async function runEmbeddedAttempt(
const sessionLabel = params.sessionKey ?? params.sessionId;
const contextInjectionMode = resolveContextInjectionMode(params.config);
const agentDir = params.agentDir ?? resolveOpenClawAgentDir();
const diagnosticTrace = freezeDiagnosticTraceContext(createDiagnosticTraceContext());
const toolsRaw = params.disableTools
? []
: (() => {
const allTools = createOpenClawCodingTools({
agentId: sessionAgentId,
...buildEmbeddedAttemptToolRunContext(params),
...buildEmbeddedAttemptToolRunContext({ ...params, trace: diagnosticTrace }),
exec: {
...params.execOverrides,
elevated: params.bashElevated,
@@ -1942,6 +1947,7 @@ export async function runEmbeddedAttempt(
}
const hookCtx = {
runId: params.runId,
trace: freezeDiagnosticTraceContext(diagnosticTrace),
agentId: hookAgentId,
sessionKey: params.sessionKey,
sessionId: params.sessionId,
@@ -2173,6 +2179,7 @@ export async function runEmbeddedAttempt(
},
{
runId: params.runId,
trace: freezeDiagnosticTraceContext(diagnosticTrace),
agentId: hookAgentId,
sessionKey: params.sessionKey,
sessionId: params.sessionId,
@@ -2580,6 +2587,7 @@ export async function runEmbeddedAttempt(
},
{
runId: params.runId,
trace: freezeDiagnosticTraceContext(diagnosticTrace),
agentId: hookAgentId,
sessionKey: params.sessionKey,
sessionId: params.sessionId,
@@ -2681,6 +2689,7 @@ export async function runEmbeddedAttempt(
},
{
runId: params.runId,
trace: freezeDiagnosticTraceContext(diagnosticTrace),
agentId: hookAgentId,
sessionKey: params.sessionKey,
sessionId: params.sessionId,
@@ -2768,6 +2777,7 @@ export async function runEmbeddedAttempt(
promptErrorSource,
preflightRecovery,
sessionIdUsed,
diagnosticTrace,
bootstrapPromptWarningSignaturesSeen: bootstrapPromptWarning.warningSignaturesSeen,
bootstrapPromptWarningSignature: bootstrapPromptWarning.signature,
systemPromptReport,

View File

@@ -4,6 +4,7 @@ import type { AuthStorage, ModelRegistry } from "@mariozechner/pi-coding-agent";
import type { ThinkLevel } from "../../../auto-reply/thinking.js";
import type { SessionSystemPromptReport } from "../../../config/sessions/types.js";
import type { ContextEngine, ContextEnginePromptCacheInfo } from "../../../context-engine/types.js";
import type { DiagnosticTraceContext } from "../../../infra/diagnostic-trace-context.js";
import type { PluginHookBeforeAgentStartResult } from "../../../plugins/hook-before-agent-start.types.js";
import type { MessagingToolSend } from "../../pi-embedded-messaging.types.js";
import type { ToolErrorSummary } from "../../tool-error-summary.js";
@@ -72,6 +73,7 @@ export type EmbeddedRunAttemptResult = {
handled?: false;
};
sessionIdUsed: string;
diagnosticTrace?: DiagnosticTraceContext;
agentHarnessId?: string;
bootstrapPromptWarningSignaturesSeen?: string[];
bootstrapPromptWarningSignature?: string;

View File

@@ -1,4 +1,5 @@
import type { CliSessionBinding, SessionSystemPromptReport } from "../../config/sessions/types.js";
import type { DiagnosticTraceContext } from "../../infra/diagnostic-trace-context.js";
import type { MessagingToolSend } from "../pi-embedded-messaging.types.js";
export type EmbeddedPiAgentMeta = {
@@ -141,6 +142,7 @@ export type EmbeddedPiRunResult = {
audioAsVoice?: boolean;
}>;
meta: EmbeddedPiRunMeta;
diagnosticTrace?: DiagnosticTraceContext;
// True if a messaging tool successfully sent a message.
// Used to suppress agent's confirmation text.
didSendViaMessagingTool?: boolean;

View File

@@ -425,6 +425,39 @@ describe("before_tool_call requireApproval handling", () => {
);
});
it("passes diagnostic trace context to before_tool_call hooks", async () => {
const trace = {
traceId: "4bf92f3577b34da6a3ce929d0e0e4736",
spanId: "00f067aa0ba902b7",
traceFlags: "01",
};
hookRunner.runBeforeToolCall.mockResolvedValue(undefined);
const result = await runBeforeToolCallHook({
toolName: "bash",
params: { command: "pwd" },
toolCallId: "tool-1",
ctx: { agentId: "main", sessionKey: "main", runId: "run-1", trace },
});
expect(result.blocked).toBe(false);
const call = hookRunner.runBeforeToolCall.mock.calls[0];
expect(call?.[0]).toMatchObject({
toolName: "exec",
runId: "run-1",
toolCallId: "tool-1",
});
const toolContext = call?.[1] as { trace?: typeof trace } | undefined;
expect(toolContext).toMatchObject({
toolName: "exec",
runId: "run-1",
toolCallId: "tool-1",
trace,
});
expect(toolContext?.trace).not.toBe(trace);
expect(Object.isFrozen(toolContext?.trace)).toBe(true);
});
it("calls gateway RPC and unblocks on allow-once", async () => {
hookRunner.runBeforeToolCall.mockResolvedValue({
requireApproval: {

View File

@@ -1,4 +1,8 @@
import type { ToolLoopDetectionConfig } from "../config/types.tools.js";
import {
freezeDiagnosticTraceContext,
type DiagnosticTraceContext,
} from "../infra/diagnostic-trace-context.js";
import type { SessionState } from "../logging/diagnostic-session-state.js";
import { createSubsystemLogger } from "../logging/subsystem.js";
import { getGlobalHookRunner } from "../plugins/hook-runner-global.js";
@@ -17,6 +21,7 @@ export type HookContext = {
/** Ephemeral session UUID — regenerated on /new and /reset. */
sessionId?: string;
runId?: string;
trace?: DiagnosticTraceContext;
loopDetection?: ToolLoopDetectionConfig;
};
@@ -197,6 +202,7 @@ export async function runBeforeToolCallHook(args: {
...(args.ctx?.sessionKey && { sessionKey: args.ctx.sessionKey }),
...(args.ctx?.sessionId && { sessionId: args.ctx.sessionId }),
...(args.ctx?.runId && { runId: args.ctx.runId }),
...(args.ctx?.trace && { trace: freezeDiagnosticTraceContext(args.ctx.trace) }),
...(args.toolCallId && { toolCallId: args.toolCallId }),
};
const hookResult = await hookRunner.runBeforeToolCall(

View File

@@ -2,6 +2,7 @@ import { createCodingTools, createReadTool } from "@mariozechner/pi-coding-agent
import type { ModelCompatConfig } from "../config/types.models.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import type { ToolLoopDetectionConfig } from "../config/types.tools.js";
import type { DiagnosticTraceContext } from "../infra/diagnostic-trace-context.js";
import { resolveMergedSafeBinProfileFixtures } from "../infra/exec-safe-bin-runtime-policy.js";
import { logWarn } from "../logger.js";
import { getPluginToolMeta } from "../plugins/tools.js";
@@ -268,6 +269,8 @@ export function createOpenClawCodingTools(options?: {
sessionId?: string;
/** Stable run identifier for this agent invocation. */
runId?: string;
/** Diagnostic trace context for hook/log correlation during this run. */
trace?: DiagnosticTraceContext;
/** What initiated this run (for trigger-specific tool restrictions). */
trigger?: string;
/** Relative workspace path that memory-triggered writes may append to. */
@@ -707,6 +710,7 @@ export function createOpenClawCodingTools(options?: {
sessionKey: options?.sessionKey,
sessionId: options?.sessionId,
runId: options?.runId,
...(options?.trace ? { trace: options.trace } : {}),
loopDetection: resolveToolLoopDetectionConfig({ cfg: options?.config, agentId }),
}),
);

View File

@@ -17,6 +17,7 @@ import type { TypingMode } from "../../config/types.js";
import { resolveSessionTranscriptCandidates } from "../../gateway/session-utils.fs.js";
import { emitAgentEvent } from "../../infra/agent-events.js";
import { emitDiagnosticEvent, isDiagnosticsEnabled } from "../../infra/diagnostic-events.js";
import { freezeDiagnosticTraceContext } from "../../infra/diagnostic-trace-context.js";
import { enqueueSystemEvent } from "../../infra/system-events.js";
import { CommandLaneClearedError, GatewayDrainingError } from "../../process/command-queue.js";
import { normalizeOptionalString } from "../../shared/string-coerce.js";
@@ -1423,6 +1424,9 @@ export async function runReplyAgent(params: {
const costUsd = estimateUsageCost({ usage, cost: costConfig });
emitDiagnosticEvent({
type: "model.usage",
...(runResult.diagnosticTrace
? { trace: freezeDiagnosticTraceContext(runResult.diagnosticTrace) }
: {}),
sessionKey,
sessionId: followupRun.run.sessionId,
channel: replyToChannel,

View File

@@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";
import {
createChildDiagnosticTraceContext,
createDiagnosticTraceContext,
freezeDiagnosticTraceContext,
formatDiagnosticTraceparent,
isValidDiagnosticSpanId,
isValidDiagnosticTraceFlags,
@@ -113,4 +114,17 @@ describe("diagnostic-trace-context", () => {
createChildDiagnosticTraceContext(parent, { spanId: SPAN_ID }).parentSpanId,
).toBeUndefined();
});
it("freezes a defensive trace context copy", () => {
const context = createDiagnosticTraceContext({
traceId: TRACE_ID,
spanId: SPAN_ID,
traceFlags: "01",
});
const frozen = freezeDiagnosticTraceContext(context);
expect(frozen).toEqual(context);
expect(frozen).not.toBe(context);
expect(Object.isFrozen(frozen)).toBe(true);
});
});

View File

@@ -10,13 +10,13 @@ const TRACEPARENT_VERSION_RE = /^[0-9a-f]{2}$/;
export type DiagnosticTraceContext = {
/** W3C trace id, 32 lowercase hex chars. */
traceId: string;
readonly traceId: string;
/** Current span id, 16 lowercase hex chars. */
spanId?: string;
readonly spanId?: string;
/** Parent span id, 16 lowercase hex chars. */
parentSpanId?: string;
readonly parentSpanId?: string;
/** W3C trace flags, 2 lowercase hex chars. Defaults to sampled. */
traceFlags?: string;
readonly traceFlags?: string;
};
export type DiagnosticTraceContextInput = Partial<DiagnosticTraceContext> & {
@@ -156,3 +156,14 @@ export function createChildDiagnosticTraceContext(
traceFlags: input.traceFlags ?? parent.traceFlags,
});
}
export function freezeDiagnosticTraceContext(
context: DiagnosticTraceContext,
): DiagnosticTraceContext {
return Object.freeze({
traceId: context.traceId,
...(context.spanId ? { spanId: context.spanId } : {}),
...(context.parentSpanId ? { parentSpanId: context.parentSpanId } : {}),
...(context.traceFlags ? { traceFlags: context.traceFlags } : {}),
});
}

View File

@@ -95,6 +95,7 @@ export type { ReplyPayload } from "./reply-payload.js";
export type { WizardPrompter } from "../wizard/prompts.js";
export type { ContextEngineFactory } from "../context-engine/registry.js";
export type { DiagnosticEventPayload } from "../infra/diagnostic-events.js";
export type { DiagnosticTraceContext } from "../infra/diagnostic-trace-context.js";
export type {
AssembleResult,
BootstrapResult,

View File

@@ -7,6 +7,7 @@ import type {
import type { FinalizedMsgContext } from "../auto-reply/templating.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import type { TtsAutoMode } from "../config/types.tts.js";
import type { DiagnosticTraceContext } from "../infra/diagnostic-trace-context.js";
import {
PLUGIN_PROMPT_MUTATION_RESULT_FIELDS,
stripPromptMutationFieldsFromLegacyHookResult,
@@ -153,6 +154,7 @@ export const isConversationHookName = (hookName: PluginHookName): boolean =>
export type PluginHookAgentContext = {
runId?: string;
trace?: DiagnosticTraceContext;
agentId?: string;
sessionKey?: string;
sessionId?: string;
@@ -300,6 +302,7 @@ export type PluginHookToolContext = {
sessionKey?: string;
sessionId?: string;
runId?: string;
trace?: DiagnosticTraceContext;
toolName: string;
toolCallId?: string;
};