





















@@ -19,6 +19,7 @@ import {
1919resetDiagnosticEventsForTest,
2020waitForDiagnosticEventsDrained,
2121type DiagnosticEventPayload,
22+type DiagnosticEventPrivateData,
2223} from "openclaw/plugin-sdk/diagnostic-runtime";
2324import {
2425clearInternalHooks,
@@ -27,7 +28,10 @@ import {
2728resetGlobalHookRunner,
2829} from "openclaw/plugin-sdk/hook-runtime";
2930import { clearPluginCommands, registerPluginCommand } from "openclaw/plugin-sdk/plugin-runtime";
30-import { createMockPluginRegistry } from "openclaw/plugin-sdk/plugin-test-runtime";
31+import {
32+createMockPluginRegistry,
33+onTrustedInternalDiagnosticEvent,
34+} from "openclaw/plugin-sdk/plugin-test-runtime";
3135import { registerSandboxBackend } from "openclaw/plugin-sdk/sandbox";
3236import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3337import WebSocket from "ws";
@@ -6744,6 +6748,129 @@ describe("runCodexAppServerAttempt", () => {
67446748expect(agentEndContext.sessionId).toBe("session-1");
67456749});
674667506751+it("emits gated model-call content diagnostics for codex turns", async () => {
6752+const diagnosticEvents: DiagnosticEventPayload[] = [];
6753+const diagnosticContentByType = new Map<string, DiagnosticEventPrivateData>();
6754+let diagnosticTypesAtLlmOutput: string[] = [];
6755+const llmOutput = vi.fn(() => {
6756+diagnosticTypesAtLlmOutput = diagnosticEvents.map((event) => event.type);
6757+});
6758+initializeGlobalHookRunner(
6759+createMockPluginRegistry([{ hookName: "llm_output", handler: llmOutput }]),
6760+);
6761+const stopDiagnostics = onTrustedInternalDiagnosticEvent((event, _metadata, privateData) => {
6762+if (event.type.startsWith("model.call.")) {
6763+diagnosticEvents.push(event);
6764+diagnosticContentByType.set(event.type, privateData);
6765+}
6766+});
6767+try {
6768+const sessionFile = path.join(tempDir, "session.jsonl");
6769+const workspaceDir = path.join(tempDir, "workspace");
6770+testing.setOpenClawCodingToolsFactoryForTests(() => [createRuntimeDynamicTool("message")]);
6771+const harness = createStartedThreadHarness();
6772+const params = createParams(sessionFile, workspaceDir);
6773+params.disableTools = false;
6774+params.runtimePlan = createCodexRuntimePlanFixture();
6775+params.config = {
6776+diagnostics: {
6777+enabled: true,
6778+otel: {
6779+enabled: true,
6780+traces: true,
6781+captureContent: {
6782+enabled: true,
6783+inputMessages: true,
6784+outputMessages: true,
6785+systemPrompt: true,
6786+toolDefinitions: true,
6787+},
6788+},
6789+},
6790+} as never;
6791+const run = runCodexAppServerAttempt(params);
6792+await harness.waitForMethod("turn/start");
6793+await harness.notify({
6794+method: "item/agentMessage/delta",
6795+params: {
6796+threadId: "thread-1",
6797+turnId: "turn-1",
6798+itemId: "msg-1",
6799+delta: "hello back",
6800+},
6801+});
6802+await harness.completeTurn({ threadId: "thread-1", turnId: "turn-1" });
6803+await run;
6804+await flushDiagnosticEvents();
6805+6806+const startedEvent = diagnosticEvents.find((event) => event.type === "model.call.started");
6807+const completedEvent = diagnosticEvents.find(
6808+(event) => event.type === "model.call.completed",
6809+);
6810+expect(startedEvent?.callId).toBe("run-1:codex-model:1");
6811+expect(startedEvent?.trace?.traceId).toBeTypeOf("string");
6812+expect(JSON.stringify(startedEvent)).not.toContain("hello");
6813+const startedContent = diagnosticContentByType.get("model.call.started")?.modelContent;
6814+expect(JSON.stringify(startedContent?.inputMessages)).toContain("hello");
6815+expect(startedContent?.systemPrompt).toContain(
6816+"You are a personal agent running inside OpenClaw.",
6817+);
6818+expect(startedContent?.toolDefinitions).toContainEqual(
6819+expect.objectContaining({
6820+name: "message",
6821+parameters: expect.objectContaining({
6822+type: "object",
6823+additionalProperties: false,
6824+}),
6825+}),
6826+);
6827+expect(completedEvent?.callId).toBe("run-1:codex-model:1");
6828+expect(JSON.stringify(completedEvent)).not.toContain("hello back");
6829+expect(
6830+JSON.stringify(diagnosticContentByType.get("model.call.completed")?.modelContent),
6831+).toContain("hello back");
6832+expect(completedEvent?.requestPayloadBytes).toBeGreaterThan(0);
6833+expect(llmOutput).toHaveBeenCalledTimes(1);
6834+expect(diagnosticTypesAtLlmOutput).toContain("model.call.completed");
6835+expect(diagnosticTypesAtLlmOutput).not.toContain("model.call.error");
6836+} finally {
6837+stopDiagnostics();
6838+}
6839+});
6840+6841+it("classifies codex model-call timeout diagnostics", async () => {
6842+const diagnosticEvents: DiagnosticEventPayload[] = [];
6843+const stopDiagnostics = onInternalDiagnosticEvent((event) => {
6844+if (event.type.startsWith("model.call.")) {
6845+diagnosticEvents.push(event);
6846+}
6847+});
6848+try {
6849+const sessionFile = path.join(tempDir, "session.jsonl");
6850+const workspaceDir = path.join(tempDir, "workspace");
6851+const harness = createStartedThreadHarness();
6852+const params = createParams(sessionFile, workspaceDir);
6853+params.config = {
6854+diagnostics: { enabled: true, otel: { enabled: true, traces: true } },
6855+} as never;
6856+params.timeoutMs = 200;
6857+6858+const run = runCodexAppServerAttempt(params, { turnCompletionIdleTimeoutMs: 5 });
6859+await harness.waitForMethod("turn/start");
6860+const result = await run;
6861+await flushDiagnosticEvents();
6862+6863+const errorEvent = diagnosticEvents.find((event) => event.type === "model.call.error") as
6864+| ({ failureKind?: string; errorCategory?: string } & DiagnosticEventPayload)
6865+| undefined;
6866+expect(result.timedOut).toBe(true);
6867+expect(errorEvent?.failureKind).toBe("timeout");
6868+expect(errorEvent?.errorCategory).toBe("timeout");
6869+} finally {
6870+stopDiagnostics();
6871+}
6872+});
6873+67476874it("waits for agent_end hooks before resolving local codex turns", async () => {
67486875let releaseAgentEnd: () => void = () => undefined;
67496876const agentEndSettled = new Promise<void>((resolve) => {
@@ -9450,6 +9577,12 @@ describe("runCodexAppServerAttempt", () => {
94509577});
9451957894529579it("times out turn start before the active run handle is installed", async () => {
9580+const diagnosticEvents: DiagnosticEventPayload[] = [];
9581+const stopDiagnostics = onInternalDiagnosticEvent((event) => {
9582+if (event.type.startsWith("model.call.")) {
9583+diagnosticEvents.push(event);
9584+}
9585+});
94539586const request = vi.fn(
94549587async (method: string, _params?: unknown, options?: { timeoutMs?: number }) => {
94559588if (method === "thread/start") {
@@ -9476,9 +9609,23 @@ describe("runCodexAppServerAttempt", () => {
94769609path.join(tempDir, "workspace"),
94779610);
94789611params.timeoutMs = 1;
9612+params.config = {
9613+diagnostics: { enabled: true, otel: { enabled: true, traces: true } },
9614+} as never;
947996159480-await expect(runCodexAppServerAttempt(params)).rejects.toThrow("turn/start timed out");
9481-expect(queueActiveRunMessageForTest("session-1", "after timeout")).toBe(false);
9616+try {
9617+await expect(runCodexAppServerAttempt(params)).rejects.toThrow("turn/start timed out");
9618+await flushDiagnosticEvents();
9619+9620+const errorEvent = diagnosticEvents.find((event) => event.type === "model.call.error") as
9621+| ({ failureKind?: string; errorCategory?: string } & DiagnosticEventPayload)
9622+| undefined;
9623+expect(errorEvent?.failureKind).toBe("timeout");
9624+expect(errorEvent?.errorCategory).toBe("timeout");
9625+expect(queueActiveRunMessageForTest("session-1", "after timeout")).toBe(false);
9626+} finally {
9627+stopDiagnostics();
9628+}
94829629});
9483963094849631it("keeps extended history enabled when resuming a bound Codex thread", async () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。