


















@@ -9,6 +9,11 @@ import {
99} from "openclaw/plugin-sdk/agent-harness";
1010import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
1111import { __testing as nativeHookRelayTesting } from "../../../../src/agents/harness/native-hook-relay.js";
12+import {
13+onAgentEvent,
14+resetAgentEventsForTest,
15+type AgentEventPayload,
16+} from "../../../../src/infra/agent-events.js";
1217import {
1318initializeGlobalHookRunner,
1419resetGlobalHookRunner,
@@ -276,12 +281,14 @@ function extractRelayIdFromThreadRequest(params: unknown): string {
276281277282describe("runCodexAppServerAttempt", () => {
278283beforeEach(async () => {
284+resetAgentEventsForTest();
279285tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-codex-run-"));
280286});
281287282288afterEach(async () => {
283289__testing.resetCodexAppServerClientFactoryForTests();
284290nativeHookRelayTesting.clearNativeHookRelaysForTests();
291+resetAgentEventsForTest();
285292resetGlobalHookRunner();
286293vi.restoreAllMocks();
287294await fs.rm(tempDir, { recursive: true, force: true });
@@ -341,6 +348,9 @@ describe("runCodexAppServerAttempt", () => {
341348const llmInput = vi.fn();
342349const llmOutput = vi.fn();
343350const agentEnd = vi.fn();
351+const onRunAgentEvent = vi.fn();
352+const globalAgentEvents: AgentEventPayload[] = [];
353+onAgentEvent((event) => globalAgentEvents.push(event));
344354initializeGlobalHookRunner(
345355createMockPluginRegistry([
346356{ hookName: "llm_input", handler: llmInput },
@@ -354,7 +364,9 @@ describe("runCodexAppServerAttempt", () => {
354364sessionManager.appendMessage(assistantMessage("existing context", Date.now()));
355365const harness = createStartedThreadHarness();
356366357-const run = runCodexAppServerAttempt(createParams(sessionFile, workspaceDir));
367+const params = createParams(sessionFile, workspaceDir);
368+params.onAgentEvent = onRunAgentEvent;
369+const run = runCodexAppServerAttempt(params);
358370await harness.waitForMethod("turn/start");
359371await vi.waitFor(() => expect(llmInput).toHaveBeenCalledTimes(1), { interval: 1 });
360372@@ -391,6 +403,56 @@ describe("runCodexAppServerAttempt", () => {
391403expect(result.assistantTexts).toEqual(["hello back"]);
392404await vi.waitFor(() => expect(llmOutput).toHaveBeenCalledTimes(1), { interval: 1 });
393405await vi.waitFor(() => expect(agentEnd).toHaveBeenCalledTimes(1), { interval: 1 });
406+const agentEvents = onRunAgentEvent.mock.calls.map(([event]) => event);
407+expect(agentEvents).toEqual(
408+expect.arrayContaining([
409+{
410+stream: "lifecycle",
411+data: expect.objectContaining({
412+phase: "start",
413+startedAt: expect.any(Number),
414+}),
415+},
416+{
417+stream: "assistant",
418+data: { text: "hello back" },
419+},
420+{
421+stream: "lifecycle",
422+data: expect.objectContaining({
423+phase: "end",
424+startedAt: expect.any(Number),
425+endedAt: expect.any(Number),
426+}),
427+},
428+]),
429+);
430+const startIndex = agentEvents.findIndex(
431+(event) => event.stream === "lifecycle" && event.data.phase === "start",
432+);
433+const assistantIndex = agentEvents.findIndex((event) => event.stream === "assistant");
434+const endIndex = agentEvents.findIndex(
435+(event) => event.stream === "lifecycle" && event.data.phase === "end",
436+);
437+expect(startIndex).toBeGreaterThanOrEqual(0);
438+expect(assistantIndex).toBeGreaterThan(startIndex);
439+expect(endIndex).toBeGreaterThan(assistantIndex);
440+expect(globalAgentEvents).toEqual(
441+expect.arrayContaining([
442+expect.objectContaining({
443+runId: "run-1",
444+sessionKey: "agent:main:session-1",
445+stream: "assistant",
446+data: { text: "hello back" },
447+}),
448+expect.objectContaining({
449+runId: "run-1",
450+sessionKey: "agent:main:session-1",
451+stream: "lifecycle",
452+data: expect.objectContaining({ phase: "end" }),
453+}),
454+]),
455+);
394456395457expect(llmOutput).toHaveBeenCalledWith(
396458expect.objectContaining({
@@ -530,14 +592,17 @@ describe("runCodexAppServerAttempt", () => {
530592531593it("fires agent_end with failure metadata when the codex turn fails", async () => {
532594const agentEnd = vi.fn();
595+const onRunAgentEvent = vi.fn();
533596initializeGlobalHookRunner(
534597createMockPluginRegistry([{ hookName: "agent_end", handler: agentEnd }]),
535598);
536599const sessionFile = path.join(tempDir, "session.jsonl");
537600const workspaceDir = path.join(tempDir, "workspace");
538601const harness = createStartedThreadHarness();
539602540-const run = runCodexAppServerAttempt(createParams(sessionFile, workspaceDir));
603+const params = createParams(sessionFile, workspaceDir);
604+params.onAgentEvent = onRunAgentEvent;
605+const run = runCodexAppServerAttempt(params);
541606await harness.waitForMethod("turn/start");
542607await harness.notify({
543608method: "turn/completed",
@@ -556,6 +621,25 @@ describe("runCodexAppServerAttempt", () => {
556621557622expect(result.promptError).toBe("codex exploded");
558623await vi.waitFor(() => expect(agentEnd).toHaveBeenCalledTimes(1), { interval: 1 });
624+const agentEvents = onRunAgentEvent.mock.calls.map(([event]) => event);
625+expect(agentEvents).toEqual(
626+expect.arrayContaining([
627+{
628+stream: "lifecycle",
629+data: expect.objectContaining({ phase: "start", startedAt: expect.any(Number) }),
630+},
631+{
632+stream: "lifecycle",
633+data: expect.objectContaining({
634+phase: "error",
635+startedAt: expect.any(Number),
636+endedAt: expect.any(Number),
637+error: "codex exploded",
638+}),
639+},
640+]),
641+);
642+expect(agentEvents.some((event) => event.stream === "assistant")).toBe(false);
559643expect(agentEnd).toHaveBeenCalledWith(
560644expect.objectContaining({
561645success: false,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。