diff --git a/scripts/plugin-sdk-surface-report.mts b/scripts/plugin-sdk-surface-report.mts index 149eb7cc6d42..c9ec409aad10 100644 --- a/scripts/plugin-sdk-surface-report.mts +++ b/scripts/plugin-sdk-surface-report.mts @@ -284,7 +284,8 @@ export function readPluginSdkSurfaceBudgets(env: NodeJS.ProcessEnv = process.env // +6: load-only bridges for published pre-split plugin artifacts // (voice-call/matrix runtime-doctor repair names, WhatsApp ack policy, // Slack progress-draft render) so installed plugins survive upgrade (#124041 class). - 4330, + // -1: remove the orphan diagnostic traceparent propagation export. + 4329, env, ), publicFunctionExports: readPluginSdkSurfaceBudgetEnv( @@ -357,7 +358,8 @@ export function readPluginSdkSurfaceBudgets(env: NodeJS.ProcessEnv = process.env // +3: load-only bridges for published pre-split plugin artifacts // (voice-call/matrix runtime-doctor repair names, WhatsApp ack policy, // Slack progress-draft render) so installed plugins survive upgrade (#124041 class). - 2577, + // -1: remove the orphan diagnostic traceparent propagation export. + 2576, env, ), publicDeprecatedExports: readPluginSdkSurfaceBudgetEnv( diff --git a/src/commands/agent-via-gateway.test.ts b/src/commands/agent-via-gateway.test.ts index 238e30d9ed2e..fe4e21b19256 100644 --- a/src/commands/agent-via-gateway.test.ts +++ b/src/commands/agent-via-gateway.test.ts @@ -360,10 +360,18 @@ describe("agentCliCommand", () => { expect(zeroTimeoutGatewayRequestMs).toBe(2_147_000_000); }); - it("clamps oversized gateway timeout seconds", () => { - expect(agentViaGatewayTesting.resolveGatewayAgentTimeoutMs(Number.MAX_SAFE_INTEGER)).toBe( - MAX_TIMER_TIMEOUT_MS, - ); + it("clamps oversized gateway timeout seconds at the command boundary", async () => { + await withTempStore(async () => { + mockGatewaySuccessReply(); + + await agentCliCommand( + { message: "hi", to: "+1555", timeout: String(Number.MAX_SAFE_INTEGER) }, + runtime, + ); + + const request = requireFirstCallArg(callGateway, "gateway") as { timeoutMs?: number }; + expect(request.timeoutMs).toBe(MAX_TIMER_TIMEOUT_MS); + }); }); it("rejects partial gateway timeout values", async () => { diff --git a/src/commands/agent-via-gateway.ts b/src/commands/agent-via-gateway.ts index 36d8f471e22d..e3896846527d 100644 --- a/src/commands/agent-via-gateway.ts +++ b/src/commands/agent-via-gateway.ts @@ -357,7 +357,6 @@ export const agentViaGatewayTesting = { agentSessionModuleCache.clear(); agentSessionModuleLoader = loader; }, - resolveGatewayAgentTimeoutMs, setGatewayAbortRetryDelaysMsForTests(delays?: readonly number[]): void { gatewayAbortRetryDelaysMsForTests = delays; }, diff --git a/src/docker-healthcheck.test.ts b/src/docker-healthcheck.test.ts index b235d7e984c2..1d7a0f830a6a 100644 --- a/src/docker-healthcheck.test.ts +++ b/src/docker-healthcheck.test.ts @@ -1,19 +1,22 @@ import { describe, expect, it, vi } from "vitest"; -import { probeDockerGatewayHealth, resolveDockerHealthcheckPort } from "./docker-healthcheck.js"; +import { probeDockerGatewayHealth } from "./docker-healthcheck.js"; describe("Docker healthcheck", () => { - it("prefers the active Gateway lock port used by --port", async () => { + it("probes the active Gateway lock port used by --port", async () => { const getRuntimeConfig = vi.fn(() => ({ gateway: { port: 19002 } })); const resolveGatewayPort = vi.fn(() => 19003); + const fetch = vi.fn(async () => ({ ok: true }) as Response); await expect( - resolveDockerHealthcheckPort({ + probeDockerGatewayHealth({ env: { OPENCLAW_GATEWAY_PORT: "19001" }, + fetch, getRuntimeConfig, readActiveGatewayLockPort: vi.fn(async () => 19000), resolveGatewayPort, }), - ).resolves.toBe(19000); + ).resolves.toBe(true); + expect(fetch).toHaveBeenCalledWith("http://127.0.0.1:19000/healthz"); expect(getRuntimeConfig).not.toHaveBeenCalled(); expect(resolveGatewayPort).not.toHaveBeenCalled(); }); @@ -31,30 +34,24 @@ describe("Docker healthcheck", () => { config: { gateway: { port: 19002 } }, expected: 19002, }, - ])("falls back to the canonical $name port", async ({ env, config, expected }) => { - await expect( - resolveDockerHealthcheckPort({ - env, - getRuntimeConfig: () => config, - readActiveGatewayLockPort: vi.fn(async () => undefined), - }), - ).resolves.toBe(expected); - }); + ])( + "probes the canonical $name port when no active lock exists", + async ({ env, config, expected }) => { + const fetch = vi.fn(async () => ({ ok: true }) as Response); - it("falls back to config when the active lock cannot be read", async () => { - await expect( - resolveDockerHealthcheckPort({ - env: {}, - getRuntimeConfig: () => ({ gateway: { port: 19002 } }), - readActiveGatewayLockPort: vi.fn(async () => { - throw new Error("lock unavailable"); + await expect( + probeDockerGatewayHealth({ + env, + fetch, + getRuntimeConfig: () => config, + readActiveGatewayLockPort: vi.fn(async () => undefined), }), - resolveGatewayPort: (config) => config.gateway?.port ?? 18789, - }), - ).resolves.toBe(19002); - }); + ).resolves.toBe(true); + expect(fetch).toHaveBeenCalledWith(`http://127.0.0.1:${expected}/healthz`); + }, + ); - it("probes the unauthenticated liveness endpoint on the resolved port", async () => { + it("probes the configured port when the active lock cannot be read", async () => { const fetch = vi.fn(async () => ({ ok: true }) as Response); await expect( @@ -62,11 +59,13 @@ describe("Docker healthcheck", () => { env: {}, fetch, getRuntimeConfig: () => ({ gateway: { port: 19002 } }), - readActiveGatewayLockPort: vi.fn(async () => 19000), - resolveGatewayPort: vi.fn(() => 19002), + readActiveGatewayLockPort: vi.fn(async () => { + throw new Error("lock unavailable"); + }), + resolveGatewayPort: (config) => config.gateway?.port ?? 18789, }), ).resolves.toBe(true); - expect(fetch).toHaveBeenCalledWith("http://127.0.0.1:19000/healthz"); + expect(fetch).toHaveBeenCalledWith("http://127.0.0.1:19002/healthz"); }); it("reports an unsuccessful or unreachable liveness endpoint as unhealthy", async () => { diff --git a/src/docker-healthcheck.ts b/src/docker-healthcheck.ts index a39abb03d60b..7a83eea5ffbb 100644 --- a/src/docker-healthcheck.ts +++ b/src/docker-healthcheck.ts @@ -17,7 +17,7 @@ type DockerHealthcheckDeps = Partial & { fetch?: typeof globalThis.fetch; }; -export async function resolveDockerHealthcheckPort( +async function resolveDockerHealthcheckPort( deps: Partial = {}, ): Promise { const env = deps.env ?? process.env; diff --git a/src/gateway/server-methods/usage.test.ts b/src/gateway/server-methods/usage.test.ts index 1d1050fc0fb0..407a85244b2e 100644 --- a/src/gateway/server-methods/usage.test.ts +++ b/src/gateway/server-methods/usage.test.ts @@ -95,8 +95,8 @@ describe("gateway usage helpers", () => { endDate: string, ) { const range = expectDateRange(result); - expect(range.startMs).toBe(testApi.parseDateToMs(startDate)); - expect(range.endMs).toBe(testApi.parseDateToMs(endDate)! + dayMs - 1); + expect(range.startMs).toBe(Date.parse(`${startDate}T00:00:00.000Z`)); + expect(range.endMs).toBe(Date.parse(`${endDate}T00:00:00.000Z`) + dayMs - 1); } function expectDateRange(result: ReturnType) { @@ -128,26 +128,6 @@ describe("gateway usage helpers", () => { vi.clearAllMocks(); }); - it("parseDateToMs accepts YYYY-MM-DD and rejects invalid input", () => { - expect(testApi.parseDateToMs("2026-02-05")).toBe(Date.UTC(2026, 1, 5)); - expect(testApi.parseDateToMs(" 2026-02-05 ")).toBe(Date.UTC(2026, 1, 5)); - expect(testApi.parseDateToMs("2026-2-5")).toBeUndefined(); - expect(testApi.parseDateToMs("nope")).toBeUndefined(); - expect(testApi.parseDateToMs(undefined)).toBeUndefined(); - }); - - it("parseDateToMs rejects out-of-range calendar dates instead of rolling them over", () => { - // Impossible dates that still match the YYYY-MM-DD shape must not silently shift to a real day. - expect(testApi.parseDateToMs("2026-02-30")).toBeUndefined(); // would roll to Mar 2 - expect(testApi.parseDateToMs("2026-04-31")).toBeUndefined(); // would roll to May 1 - expect(testApi.parseDateToMs("2025-02-29")).toBeUndefined(); // non-leap Feb 29 - expect(testApi.parseDateToMs("2026-13-01")).toBeUndefined(); // month too large - expect(testApi.parseDateToMs("2026-00-10")).toBeUndefined(); // month zero - expect(testApi.parseDateToMs("2026-01-00")).toBeUndefined(); // day zero - // Real leap day must stay valid (guard against over-rejection). - expect(testApi.parseDateToMs("2024-02-29")).toBe(Date.UTC(2024, 1, 29)); - }); - it.each([ [{ startDate: "2026-02-30" }, "invalid startDate"], [{ endDate: "2026-2-5" }, "invalid endDate"], @@ -275,34 +255,6 @@ describe("gateway usage helpers", () => { expect(vi.mocked(discoverAllSessions)).not.toHaveBeenCalled(); }); - it("parseUtcOffsetToMinutes supports whole-hour and half-hour offsets", () => { - expect(testApi.parseUtcOffsetToMinutes("UTC-4")).toBe(-240); - expect(testApi.parseUtcOffsetToMinutes("UTC+5:30")).toBe(330); - expect(testApi.parseUtcOffsetToMinutes(" UTC+14 ")).toBe(14 * 60); - }); - - it("parseUtcOffsetToMinutes rejects invalid offsets", () => { - expect(testApi.parseUtcOffsetToMinutes("UTC+14:30")).toBeUndefined(); - expect(testApi.parseUtcOffsetToMinutes("UTC+5:99")).toBeUndefined(); - expect(testApi.parseUtcOffsetToMinutes("UTC+25")).toBeUndefined(); - expect(testApi.parseUtcOffsetToMinutes("GMT+5")).toBeUndefined(); - expect(testApi.parseUtcOffsetToMinutes(undefined)).toBeUndefined(); - }); - - it("parseDays coerces strings/numbers to integers", () => { - expect(testApi.parseDays(7.9)).toBe(7); - expect(testApi.parseDays("30")).toBe(30); - expect(testApi.parseDays("")).toBeUndefined(); - expect(testApi.parseDays("nope")).toBeUndefined(); - }); - - it("parseDays caps day counts before Date arithmetic can overflow", () => { - expect(testApi.parseDays(1e300)).toBe(36600); - expect(testApi.parseDays("1e300")).toBe(36600); - expect(testApi.parseDays(Number.MAX_SAFE_INTEGER)).toBe(36600); - expect(testApi.parseDays(366 * 100)).toBe(36600); - }); - it("resolveDateRange uses explicit start/end as UTC when mode is missing (backward compatible)", () => { const result = testApi.resolveDateRange({ startDate: "2026-02-01", @@ -311,13 +263,13 @@ describe("gateway usage helpers", () => { expectUtcDateRange(result, "2026-02-01", "2026-02-02"); }); - it("resolveDateRange uses explicit UTC mode", () => { + it("resolveDateRange accepts a leap day in explicit UTC mode", () => { const result = testApi.resolveDateRange({ - startDate: "2026-02-01", - endDate: "2026-02-02", + startDate: "2024-02-29", + endDate: "2024-03-01", mode: "utc", }); - expectUtcDateRange(result, "2026-02-01", "2026-02-02"); + expectUtcDateRange(result, "2024-02-29", "2024-03-01"); }); it("resolveDateRange uses specific UTC offset for explicit dates", () => { @@ -389,7 +341,7 @@ describe("gateway usage helpers", () => { startDate: "2026-02-01", endDate: "2026-02-02", mode: "specific", - utcOffset: "bad-value", + utcOffset: "UTC+14:30", }), ); expect(missingOffset.startMs).toBe(Date.UTC(2026, 1, 1)); diff --git a/src/gateway/server-methods/usage.ts b/src/gateway/server-methods/usage.ts index 28fa265ab25d..5567430e31c7 100644 --- a/src/gateway/server-methods/usage.ts +++ b/src/gateway/server-methods/usage.ts @@ -522,21 +522,6 @@ const getDateParts = (date: Date, interpretation: DateInterpretation): DateParts }; }; -/** - * Parse a date string (YYYY-MM-DD) to start-of-day timestamp based on interpretation mode. - * Returns undefined if invalid. - */ -const parseDateToMs = ( - raw: unknown, - interpretation: DateInterpretation = { mode: "utc" }, -): number | undefined => { - const parts = parseDateParts(raw); - if (!parts) { - return undefined; - } - return datePartsToStartMs(parts, interpretation); -}; - const formatDateLabel = (ms: number, interpretation: DateInterpretation): string => { const parts = getDateParts(new Date(ms), interpretation); return formatDateParts(parts.year, parts.monthIndex, parts.day); @@ -1141,9 +1126,6 @@ function mergeUsageCacheStatus( // Exposed for unit tests (kept as a single export to avoid widening the public API surface). export const testApi = { - parseUtcOffsetToMinutes, - parseDateToMs, - parseDays, resolveDateRange, loadCostUsageSummaryCached, costUsageCache, diff --git a/src/infra/diagnostic-events.test.ts b/src/infra/diagnostic-events.test.ts index f622f1f6ea5f..b0d1bacffacc 100644 --- a/src/infra/diagnostic-events.test.ts +++ b/src/infra/diagnostic-events.test.ts @@ -9,7 +9,6 @@ import { emitTrustedDiagnosticEventWithPrivateData, emitTrustedSkillUsedDiagnosticEvent, emitTrustedSecurityEvent, - formatDiagnosticTraceparentForPropagation, hasPendingInternalDiagnosticEvent, isInternalDiagnosticEventMetadata, isDiagnosticsEnabled, @@ -246,38 +245,6 @@ describe("diagnostic-events", () => { expect(isInternalDiagnosticEventMetadata({ trusted: false })).toBe(false); }); - it("formats traceparent for propagation only from dispatcher-trusted metadata", () => { - const trace = createDiagnosticTraceContext({ - traceId: "4bf92f3577b34da6a3ce929d0e0e4736", - spanId: "00f067aa0ba902b7", - traceFlags: "01", - }); - const traceparents: Array = []; - onInternalDiagnosticEvent((event, metadata) => { - traceparents.push(formatDiagnosticTraceparentForPropagation(event, metadata)); - }); - - emitDiagnosticEvent({ - type: "message.queued", - source: "plugin", - trace, - }); - emitTrustedDiagnosticEvent({ - type: "model.usage", - usage: { total: 1 }, - trace, - }); - - expect(traceparents).toEqual([undefined, `00-${trace.traceId}-${trace.spanId}-01`]); - expect(formatDiagnosticTraceparentForPropagation({ trace }, { trusted: true })).toBeUndefined(); - expect( - formatDiagnosticTraceparentForPropagation( - { trace }, - { trusted: false, trustedTraceContext: true }, - ), - ).toBeUndefined(); - }); - it("prepares trusted events synchronously without cloning private data", async () => { const diagnosticTrace = createDiagnosticTraceContext({ traceId: "4bf92f3577b34da6a3ce929d0e0e4736", diff --git a/src/infra/diagnostic-events.ts b/src/infra/diagnostic-events.ts index 5e7219c1ae49..e4850a3952d0 100644 --- a/src/infra/diagnostic-events.ts +++ b/src/infra/diagnostic-events.ts @@ -19,7 +19,6 @@ import { type DiagnosticTraceContext, } from "./diagnostic-trace-context.js"; import { - formatPropagatedDiagnosticTraceparent, prepareDiagnosticTracePropagation, resetDiagnosticTracePropagationForTest, shouldPrepareDiagnosticTracePropagation, @@ -959,7 +958,6 @@ type DiagnosticEventsGlobalState = { const MAX_ASYNC_DIAGNOSTIC_EVENTS = 10_000; const MAX_ASYNC_DIAGNOSTIC_EVENTS_PER_TURN = 100; const DIAGNOSTIC_EVENTS_STATE_KEY = Symbol.for("openclaw.diagnosticEvents.state.v1"); -const dispatchedTrustedDiagnosticMetadata = new WeakSet(); const ASYNC_DIAGNOSTIC_EVENT_TYPES = new Set([ "tool.execution.started", "tool.execution.completed", @@ -1145,11 +1143,7 @@ function dispatchDiagnosticEvent( function createDiagnosticMetadataForListener( metadata: DiagnosticEventMetadata, ): DiagnosticEventMetadata { - const listenerMetadata = Object.freeze({ ...metadata }); - if (listenerMetadata.trusted) { - dispatchedTrustedDiagnosticMetadata.add(listenerMetadata); - } - return listenerMetadata; + return Object.freeze({ ...metadata }); } function cloneDiagnosticEventForListener(event: DiagnosticEventPayload): DiagnosticEventPayload { @@ -1585,17 +1579,6 @@ export function onDiagnosticEvent(listener: (evt: DiagnosticEventPayload) => voi }); } -/** Formats traceparent only for trusted metadata created by the diagnostic dispatcher. */ -export function formatDiagnosticTraceparentForPropagation( - event: { trace?: DiagnosticTraceContext }, - metadata: DiagnosticEventMetadata, -): string | undefined { - if (!metadata.trusted || !dispatchedTrustedDiagnosticMetadata.has(metadata)) { - return undefined; - } - return formatPropagatedDiagnosticTraceparent(event.trace); -} - /** Returns whether listener metadata marks dispatcher-internal provenance. */ export function isInternalDiagnosticEventMetadata(metadata: DiagnosticEventMetadata): boolean { return metadata.internal === true;