Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/Java/Openclaw/src/agents/pi-embedded-runner/   (KI Agentensystem Version 22©)  Datei vom 26.3.2026 mit Größe 29 kB image not shown  

Quelle  compact.hooks.test.ts

  Sprache: JAVA
 

Spracherkennung für: .ts vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

import type { AgentMessage } from "@mariozechner/pi-agent-core";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import {
  applyExtraParamsToAgentMock,
  contextEngineCompactMock,
  createOpenClawCodingToolsMock,
  ensureRuntimePluginsLoaded,
  estimateTokensMock,
  getMemorySearchManagerMock,
  hookRunner,
  loadCompactHooksHarness,
  maybeCompactAgentHarnessSessionMock,
  registerProviderStreamForModelMock,
  resolveContextEngineMock,
  resolveEmbeddedAgentStreamFnMock,
  resolveMemorySearchConfigMock,
  resolveModelMock,
  resolveSandboxContextMock,
  resolveSessionAgentIdMock,
  resetCompactHooksHarnessMocks,
  resetCompactSessionStateMocks,
  sessionAbortCompactionMock,
  sessionMessages,
  sessionCompactImpl,
  triggerInternalHook,
} from "./compact.hooks.harness.js";

let compactEmbeddedPiSessionDirect: typeof import("./compact.js").compactEmbeddedPiSessionDirect;
let compactEmbeddedPiSession: typeof import("./compact.queued.js").compactEmbeddedPiSession;
let compactTesting: typeof import("./compact.js").__testing;
let onSessionTranscriptUpdate: typeof import("../../sessions/transcript-events.js").onSessionTranscriptUpdate;

const TEST_SESSION_ID = "session-1";
const TEST_SESSION_KEY = "agent:main:session-1";
const TEST_SESSION_FILE = "/tmp/session.jsonl";
const TEST_WORKSPACE_DIR = "/tmp";
const TEST_CUSTOM_INSTRUCTIONS = "focus on decisions";
type SessionHookEvent = {
  type?: string;
  action?: string;
  sessionKey?: string;
  context?: Record<string, unknown>;
};
type PostCompactionSyncParams = {
  reason: string;
  sessionFiles: string[];
};
type PostCompactionSync = (params?: unknown) => Promise<void>;
type Deferred<T> = {
  promise: Promise<T>;
  resolve: (value: T) => void;
};

function createDeferred<T>(): Deferred<T> {
  let resolve!: (value: T) => void;
  const promise = new Promise<T>((promiseResolve) => {
    resolve = promiseResolve;
  });
  return { promise, resolve };
}

function mockResolvedModel() {
  resolveModelMock.mockReset();
  resolveModelMock.mockReturnValue({
    model: { provider: "openai", api: "responses", id: "fake", input: [] },
    error: null,
    authStorage: { setRuntimeApiKey: vi.fn() },
    modelRegistry: {},
  });
}

function compactionConfig(mode: "await" | "off" | "async") {
  return {
    agents: {
      defaults: {
        compaction: {
          postIndexSync: mode,
        },
      },
    },
  } as never;
}

function wrappedCompactionArgs(overrides: Record<string, unknown> = {}) {
  return {
    sessionId: TEST_SESSION_ID,
    sessionKey: TEST_SESSION_KEY,
    sessionFile: TEST_SESSION_FILE,
    workspaceDir: TEST_WORKSPACE_DIR,
    customInstructions: TEST_CUSTOM_INSTRUCTIONS,
    enqueue: async <T>(task: () => Promise<T> | T) => await task(),
    ...overrides,
  };
}

const sessionHook = (action: string): SessionHookEvent | undefined =>
  triggerInternalHook.mock.calls.find((call) => {
    const event = call[0] as SessionHookEvent | undefined;
    return event?.type === "session" && event.action === action;
  })?.[0] as SessionHookEvent | undefined;

async function runCompactionHooks(params: { sessionKey?: string; messageProvider?: string }) {
  const originalMessages = sessionMessages.slice(1) as AgentMessage[];
  const currentMessages = sessionMessages.slice(1) as AgentMessage[];
  const beforeMetrics = compactTesting.buildBeforeCompactionHookMetrics({
    originalMessages,
    currentMessages,
    estimateTokensFn: estimateTokensMock as (message: AgentMessage) => number,
  });

  const hookState = await compactTesting.runBeforeCompactionHooks({
    hookRunner,
    sessionId: TEST_SESSION_ID,
    sessionKey: params.sessionKey,
    sessionAgentId: "main",
    workspaceDir: TEST_WORKSPACE_DIR,
    messageProvider: params.messageProvider,
    metrics: beforeMetrics,
  });

  await compactTesting.runAfterCompactionHooks({
    hookRunner,
    sessionId: TEST_SESSION_ID,
    sessionAgentId: "main",
    hookSessionKey: hookState.hookSessionKey,
    missingSessionKey: hookState.missingSessionKey,
    workspaceDir: TEST_WORKSPACE_DIR,
    messageProvider: params.messageProvider,
    messageCountAfter: 1,
    tokensAfter: 10,
    compactedCount: 1,
    sessionFile: TEST_SESSION_FILE,
    summaryLength: "summary".length,
    tokensBefore: 120,
    firstKeptEntryId: "entry-1",
  });
}

beforeAll(async () => {
  const loaded = await loadCompactHooksHarness();
  compactEmbeddedPiSessionDirect = loaded.compactEmbeddedPiSessionDirect;
  compactEmbeddedPiSession = loaded.compactEmbeddedPiSession;
  compactTesting = loaded.__testing;
  onSessionTranscriptUpdate = loaded.onSessionTranscriptUpdate;
});

beforeEach(() => {
  resetCompactHooksHarnessMocks();
});

describe("compactEmbeddedPiSessionDirect hooks", () => {
  beforeEach(() => {
    ensureRuntimePluginsLoaded.mockReset();
    triggerInternalHook.mockClear();
    hookRunner.hasHooks.mockReset();
    hookRunner.runBeforeCompaction.mockReset();
    hookRunner.runAfterCompaction.mockReset();
    mockResolvedModel();
    sessionCompactImpl.mockReset();
    sessionCompactImpl.mockResolvedValue({
      summary: "summary",
      firstKeptEntryId: "entry-1",
      tokensBefore: 120,
      details: { ok: true },
    });
    resetCompactSessionStateMocks();
  });

  it("bootstraps runtime plugins with the resolved workspace", async () => {
    // This assertion only cares about bootstrap wiring, so stop before the
    // rest of the compaction pipeline can pull in unrelated runtime surfaces.
    resolveModelMock.mockReturnValue({
      model: undefined,
      error: "stop after bootstrap",
      authStorage: { setRuntimeApiKey: vi.fn() },
      modelRegistry: {},
    } as never);

    await compactEmbeddedPiSessionDirect({
      sessionId: "session-1",
      sessionFile: "/tmp/session.jsonl",
      workspaceDir: "/tmp/workspace",
    });

    expect(ensureRuntimePluginsLoaded).toHaveBeenCalledWith({
      config: undefined,
      workspaceDir: "/tmp/workspace",
    });
  });

  it("forwards gateway subagent binding opt-in during compaction bootstrap", async () => {
    // Coding-tool forwarding is covered elsewhere; this compaction test only
    // owns the runtime bootstrap wiring.
    resolveModelMock.mockReturnValue({
      model: undefined,
      error: "stop after bootstrap",
      authStorage: { setRuntimeApiKey: vi.fn() },
      modelRegistry: {},
    } as never);

    await compactEmbeddedPiSessionDirect({
      sessionId: "session-1",
      sessionFile: "/tmp/session.jsonl",
      workspaceDir: "/tmp/workspace",
      allowGatewaySubagentBinding: true,
    });

    expect(ensureRuntimePluginsLoaded).toHaveBeenCalledWith({
      config: undefined,
      workspaceDir: "/tmp/workspace",
      allowGatewaySubagentBinding: true,
    });
  });

  it("uses sandboxSessionKey only for compaction sandbox resolution", async () => {
    await compactEmbeddedPiSessionDirect({
      sessionId: "session-1",
      sessionKey: "agent:main:main",
      sandboxSessionKey: "agent:main:telegram:default:direct:12345",
      sessionFile: "/tmp/session.jsonl",
      workspaceDir: "/tmp/workspace",
    });

    expect(resolveSandboxContextMock).toHaveBeenCalledWith({
      config: undefined,
      sessionKey: "agent:main:telegram:default:direct:12345",
      workspaceDir: "/tmp/workspace",
    });
  });

  it("routes compaction through shared stream resolution and extra params", async () => {
    const resolvedStreamFn = vi.fn();
    resolveEmbeddedAgentStreamFnMock.mockReturnValue(resolvedStreamFn);
    applyExtraParamsToAgentMock.mockReturnValue({
      effectiveExtraParams: { transport: "websocket" },
    });
    const session = {
      agent: {
        streamFn: vi.fn(),
      },
      messages: [{ role: "user", content: "hello" }],
    };

    compactTesting.prepareCompactionSessionAgent({
      session: session as never,
      providerStreamFn: vi.fn(),
      shouldUseWebSocketTransport: false,
      sessionId: "session-1",
      signal: new AbortController().signal,
      effectiveModel: { provider: "openai", id: "fake", api: "responses", input: [] } as never,
      resolvedApiKey: undefined,
      authStorage: { setRuntimeApiKey: vi.fn() },
      config: undefined,
      provider: "openai",
      modelId: "gpt-5.4",
      thinkLevel: "off",
      sessionAgentId: "main",
      effectiveWorkspace: "/tmp/workspace",
      agentDir: "/tmp/workspace",
    });

    expect(resolveEmbeddedAgentStreamFnMock).toHaveBeenCalledWith(
      expect.objectContaining({
        currentStreamFn: expect.any(Function),
        sessionId: "session-1",
      }),
    );
    expect(applyExtraParamsToAgentMock).toHaveBeenCalledWith(
      expect.objectContaining({
        streamFn: resolvedStreamFn,
      }),
      undefined,
      "openai",
      "gpt-5.4",
      undefined,
      "off",
      "main",
      "/tmp/workspace",
      expect.objectContaining({
        provider: "openai",
        id: "fake",
        api: "responses",
      }),
      "/tmp/workspace",
      undefined,
      undefined,
    );
  });

  it("preserves full sender identity when building compaction tools", async () => {
    await compactEmbeddedPiSessionDirect({
      sessionId: "session-1",
      sessionFile: "/tmp/session.jsonl",
      workspaceDir: "/tmp/workspace",
      senderId: "sender-1",
      senderName: "Alice",
      senderUsername: "alice_u",
      senderE164: "+15551234567",
    });

    expect(createOpenClawCodingToolsMock).toHaveBeenCalledWith(
      expect.objectContaining({
        senderId: "sender-1",
        senderName: "Alice",
        senderUsername: "alice_u",
        senderE164: "+15551234567",
      }),
    );
  });

  it("emits internal + plugin compaction hooks with counts", async () => {
    hookRunner.hasHooks.mockReturnValue(true);
    await runCompactionHooks({
      sessionKey: TEST_SESSION_KEY,
      messageProvider: "telegram",
    });

    expect(sessionHook("compact:before")).toMatchObject({
      type: "session",
      action: "compact:before",
    });
    const beforeContext = sessionHook("compact:before")?.context;
    const afterContext = sessionHook("compact:after")?.context;

    expect(beforeContext).toMatchObject({
      messageCount: 2,
      tokenCount: 20,
      messageCountOriginal: 2,
      tokenCountOriginal: 20,
    });
    expect(afterContext).toMatchObject({
      messageCount: 1,
      compactedCount: 1,
    });
    expect(afterContext?.compactedCount).toBe(
      (beforeContext?.messageCountOriginal as number) - (afterContext?.messageCount as number),
    );

    expect(hookRunner.runBeforeCompaction).toHaveBeenCalledWith(
      expect.objectContaining({
        messageCount: 2,
        tokenCount: 20,
      }),
      expect.objectContaining({ sessionKey: "agent:main:session-1", messageProvider: "telegram" }),
    );
    expect(hookRunner.runAfterCompaction).toHaveBeenCalledWith(
      {
        messageCount: 1,
        tokenCount: 10,
        compactedCount: 1,
        sessionFile: "/tmp/session.jsonl",
      },
      expect.objectContaining({ sessionKey: "agent:main:session-1", messageProvider: "telegram" }),
    );
  });

  it("uses sessionId as hook session key fallback when sessionKey is missing", async () => {
    hookRunner.hasHooks.mockReturnValue(true);
    await runCompactionHooks({});

    expect(sessionHook("compact:before")?.sessionKey).toBe("session-1");
    expect(sessionHook("compact:after")?.sessionKey).toBe("session-1");
    expect(hookRunner.runBeforeCompaction).toHaveBeenCalledWith(
      expect.any(Object),
      expect.objectContaining({ sessionKey: "session-1" }),
    );
    expect(hookRunner.runAfterCompaction).toHaveBeenCalledWith(
      expect.any(Object),
      expect.objectContaining({ sessionKey: "session-1" }),
    );
  });

  it("applies validated transcript before hooks even when it becomes empty", async () => {
    hookRunner.hasHooks.mockReturnValue(true);
    const beforeMetrics = compactTesting.buildBeforeCompactionHookMetrics({
      originalMessages: [],
      currentMessages: [],
      estimateTokensFn: estimateTokensMock as (message: AgentMessage) => number,
    });
    await compactTesting.runBeforeCompactionHooks({
      hookRunner,
      sessionId: "session-1",
      sessionKey: "agent:main:session-1",
      sessionAgentId: "main",
      workspaceDir: "/tmp",
      metrics: beforeMetrics,
    });

    const beforeContext = sessionHook("compact:before")?.context;
    expect(beforeContext).toMatchObject({
      messageCountOriginal: 0,
      tokenCountOriginal: 0,
      messageCount: 0,
      tokenCount: 0,
    });
  });
  it("emits a transcript update after successful compaction", async () => {
    const listener = vi.fn();
    const cleanup = onSessionTranscriptUpdate(listener);

    try {
      await compactTesting.runPostCompactionSideEffects({
        sessionKey: "agent:main:session-1",
        sessionFile: "  /tmp/session.jsonl  ",
      });

      expect(listener).toHaveBeenCalledTimes(1);
      expect(listener).toHaveBeenCalledWith({ sessionFile: "/tmp/session.jsonl" });
    } finally {
      cleanup();
    }
  });

  it("preserves tokensAfter when full-session context exceeds result.tokensBefore", async () => {
    estimateTokensMock.mockImplementation((message: unknown) => {
      const role = (message as { role?: string }).role;
      if (role === "user") {
        return 30;
      }
      if (role === "assistant") {
        return 20;
      }
      return 5;
    });
    const tokensAfter = compactTesting.estimateTokensAfterCompaction({
      messagesAfter: [{ role: "user", content: "kept ask" }] as AgentMessage[],
      fullSessionTokensBefore: 55,
      estimateTokensFn: estimateTokensMock as (message: AgentMessage) => number,
    });

    expect(tokensAfter).toBe(30);
  });

  it("treats pre-compaction token estimation failures as a no-op sanity check", async () => {
    estimateTokensMock.mockImplementation((message: unknown) => {
      const role = (message as { role?: string }).role;
      if (role === "assistant") {
        throw new Error("legacy message");
      }
      if (role === "user") {
        return 30;
      }
      return 5;
    });
    const beforeMetrics = compactTesting.buildBeforeCompactionHookMetrics({
      originalMessages: sessionMessages as AgentMessage[],
      currentMessages: sessionMessages as AgentMessage[],
      estimateTokensFn: estimateTokensMock as (message: AgentMessage) => number,
    });
    const tokensAfter = compactTesting.estimateTokensAfterCompaction({
      messagesAfter: [{ role: "user", content: "kept ask" }] as AgentMessage[],
      fullSessionTokensBefore: 0,
      estimateTokensFn: estimateTokensMock as (message: AgentMessage) => number,
    });

    expect(beforeMetrics.tokenCountOriginal).toBeUndefined();
    expect(beforeMetrics.tokenCountBefore).toBeUndefined();
    expect(tokensAfter).toBe(30);
  });

  it("skips sync in await mode when postCompactionForce is false", async () => {
    const sync = vi.fn(async () => {});
    getMemorySearchManagerMock.mockResolvedValue({ manager: { sync } });
    resolveMemorySearchConfigMock.mockReturnValue({
      sources: ["sessions"],
      sync: {
        sessions: {
          postCompactionForce: false,
        },
      },
    });

    await compactTesting.runPostCompactionSideEffects({
      config: compactionConfig("await"),
      sessionKey: TEST_SESSION_KEY,
      sessionFile: TEST_SESSION_FILE,
    });

    expect(resolveSessionAgentIdMock).toHaveBeenCalledWith({
      sessionKey: TEST_SESSION_KEY,
      config: expect.any(Object),
    });
    expect(getMemorySearchManagerMock).not.toHaveBeenCalled();
    expect(sync).not.toHaveBeenCalled();
  });

  it("awaits post-compaction memory sync in await mode when postCompactionForce is true", async () => {
    const syncStarted = createDeferred<PostCompactionSyncParams>();
    const syncRelease = createDeferred<void>();
    const sync = vi.fn<PostCompactionSync>(async (params) => {
      syncStarted.resolve(params as PostCompactionSyncParams);
      await syncRelease.promise;
    });
    getMemorySearchManagerMock.mockResolvedValue({ manager: { sync } });
    let settled = false;

    const resultPromise = compactTesting.runPostCompactionSideEffects({
      config: compactionConfig("await"),
      sessionKey: TEST_SESSION_KEY,
      sessionFile: TEST_SESSION_FILE,
    });

    void resultPromise.then(() => {
      settled = true;
    });
    await expect(syncStarted.promise).resolves.toEqual({
      reason: "post-compaction",
      sessionFiles: [TEST_SESSION_FILE],
    });
    expect(settled).toBe(false);
    syncRelease.resolve(undefined);
    await resultPromise;
    expect(settled).toBe(true);
  });

  it("skips post-compaction memory sync when the mode is off", async () => {
    const sync = vi.fn(async () => {});
    getMemorySearchManagerMock.mockResolvedValue({ manager: { sync } });

    await compactTesting.runPostCompactionSideEffects({
      config: compactionConfig("off"),
      sessionKey: TEST_SESSION_KEY,
      sessionFile: TEST_SESSION_FILE,
    });

    expect(resolveSessionAgentIdMock).not.toHaveBeenCalled();
    expect(getMemorySearchManagerMock).not.toHaveBeenCalled();
    expect(sync).not.toHaveBeenCalled();
  });

  it("fires post-compaction memory sync without awaiting it in async mode", async () => {
    const sync = vi.fn<PostCompactionSync>(async () => {});
    const managerRequested = createDeferred<void>();
    const managerGate = createDeferred<{ manager: { sync: PostCompactionSync } }>();
    const syncStarted = createDeferred<PostCompactionSyncParams>();
    sync.mockImplementation(async (params) => {
      syncStarted.resolve(params as PostCompactionSyncParams);
    });
    getMemorySearchManagerMock.mockImplementation(async () => {
      managerRequested.resolve(undefined);
      return await managerGate.promise;
    });
    let settled = false;

    const resultPromise = compactTesting.runPostCompactionSideEffects({
      config: compactionConfig("async"),
      sessionKey: TEST_SESSION_KEY,
      sessionFile: TEST_SESSION_FILE,
    });

    await managerRequested.promise;
    void resultPromise.then(() => {
      settled = true;
    });
    await resultPromise;
    expect(getMemorySearchManagerMock).toHaveBeenCalledTimes(1);
    expect(settled).toBe(true);
    expect(sync).not.toHaveBeenCalled();
    managerGate.resolve({ manager: { sync } });
    await expect(syncStarted.promise).resolves.toEqual({
      reason: "post-compaction",
      sessionFiles: [TEST_SESSION_FILE],
    });
  });

  it("skips compaction when the transcript only contains boilerplate replies and tool output", async () => {
    const messages = [
      { role: "user", content: "<b>HEARTBEAT_OK</b>", timestamp: 1 },
      {
        role: "toolResult",
        toolCallId: "t1",
        toolName: "exec",
        content: [{ type: "text", text: "checked" }],
        isError: false,
        timestamp: 2,
      },
    ] as AgentMessage[];

    expect(compactTesting.containsRealConversationMessages(messages)).toBe(false);
  });

  it("skips compaction when the transcript only contains heartbeat boilerplate and reasoning blocks", async () => {
    const messages = [
      { role: "user", content: "<b>HEARTBEAT_OK</b>", timestamp: 1 },
      {
        role: "assistant",
        content: [{ type: "thinking", thinking: "checking" }],
        timestamp: 2,
      },
    ] as AgentMessage[];

    expect(compactTesting.containsRealConversationMessages(messages)).toBe(false);
  });

  it("does not treat assistant-only tool-call blocks as meaningful conversation", () => {
    expect(
      compactTesting.hasMeaningfulConversationContent({
        role: "assistant",
        content: [{ type: "toolCall", id: "call_1", name: "exec", arguments: {} }],
      } as AgentMessage),
    ).toBe(false);
  });

  it("counts tool output as real only when a meaningful user ask exists in the lookback window", () => {
    const heartbeatToolResultWindow = [
      { role: "user", content: "<b>HEARTBEAT_OK</b>" },
      {
        role: "toolResult",
        toolCallId: "t1",
        toolName: "exec",
        content: [{ type: "text", text: "checked" }],
      },
    ] as AgentMessage[];
    expect(
      compactTesting.hasRealConversationContent(
        heartbeatToolResultWindow[1],
        heartbeatToolResultWindow,
        1,
      ),
    ).toBe(false);

    const realAskToolResultWindow = [
      { role: "assistant", content: "NO_REPLY" },
      { role: "user", content: "please inspect the failing PR" },
      {
        role: "toolResult",
        toolCallId: "t2",
        toolName: "exec",
        content: [{ type: "text", text: "checked" }],
      },
    ] as AgentMessage[];
    expect(
      compactTesting.hasRealConversationContent(
        realAskToolResultWindow[2],
        realAskToolResultWindow,
        2,
      ),
    ).toBe(true);
  });

  it("registers the Ollama api provider before compaction", async () => {
    const streamFn = vi.fn();
    registerProviderStreamForModelMock.mockReturnValue(streamFn);

    const result = compactTesting.resolveCompactionProviderStream({
      effectiveModel: {
        provider: "ollama",
        api: "ollama",
        id: "qwen3:8b",
        input: ["text"],
        baseUrl: "http://127.0.0.1:11434",
        headers: { Authorization: "Bearer ollama-cloud" },
      } as never,
      config: undefined,
      agentDir: "/tmp",
      effectiveWorkspace: "/tmp",
    });

    expect(result).toBe(streamFn);
    expect(registerProviderStreamForModelMock).toHaveBeenCalledWith(
      expect.objectContaining({
        model: expect.objectContaining({
          provider: "ollama",
          api: "ollama",
          id: "qwen3:8b",
        }),
        agentDir: "/tmp",
        workspaceDir: "/tmp",
      }),
    );
  });

  it("aborts in-flight compaction when the caller abort signal fires", async () => {
    const { compactWithSafetyTimeout } = await vi.importActual<
      typeof import("./compaction-safety-timeout.js")
    >("./compaction-safety-timeout.js");
    const controller = new AbortController();
    const compactStarted = createDeferred<void>();

    const resultPromise = compactWithSafetyTimeout(
      async () => {
        compactStarted.resolve(undefined);
        return await new Promise<never>(() => {});
      },
      30_000,
      {
        abortSignal: controller.signal,
        onCancel: () => {
          sessionAbortCompactionMock();
        },
      },
    );

    await compactStarted.promise;
    controller.abort(new Error("request timed out"));

    await expect(resultPromise).rejects.toThrow("request timed out");
    expect(sessionAbortCompactionMock).toHaveBeenCalledTimes(1);
  });
});

describe("compactEmbeddedPiSession hooks (ownsCompaction engine)", () => {
  beforeEach(() => {
    hookRunner.hasHooks.mockReset();
    hookRunner.runBeforeCompaction.mockReset();
    hookRunner.runAfterCompaction.mockReset();
    resolveContextEngineMock.mockReset();
    resolveContextEngineMock.mockResolvedValue({
      info: { ownsCompaction: true },
      compact: contextEngineCompactMock,
    });
    contextEngineCompactMock.mockReset();
    contextEngineCompactMock.mockResolvedValue({
      ok: true,
      compacted: true,
      reason: undefined,
      result: { summary: "engine-summary", tokensAfter: 50 },
    });
    mockResolvedModel();
  });

  it("fires before_compaction with sentinel -1 and after_compaction on success", async () => {
    hookRunner.hasHooks.mockReturnValue(true);

    const result = await compactEmbeddedPiSession(
      wrappedCompactionArgs({
        messageChannel: "telegram",
      }),
    );

    expect(result.ok).toBe(true);
    expect(result.compacted).toBe(true);

    expect(hookRunner.runBeforeCompaction).toHaveBeenCalledWith(
      { messageCount: -1, sessionFile: TEST_SESSION_FILE },
      expect.objectContaining({
        sessionKey: TEST_SESSION_KEY,
        messageProvider: "telegram",
      }),
    );
    expect(hookRunner.runAfterCompaction).toHaveBeenCalledWith(
      {
        messageCount: -1,
        compactedCount: -1,
        tokenCount: 50,
        sessionFile: TEST_SESSION_FILE,
      },
      expect.objectContaining({
        sessionKey: TEST_SESSION_KEY,
        messageProvider: "telegram",
      }),
    );
  });

  it("emits a transcript update and post-compaction memory sync on the engine-owned path", async () => {
    const listener = vi.fn();
    const cleanup = onSessionTranscriptUpdate(listener);
    const sync = vi.fn(async () => {});
    getMemorySearchManagerMock.mockResolvedValue({ manager: { sync } });

    try {
      const result = await compactEmbeddedPiSession(
        wrappedCompactionArgs({
          sessionFile: `  ${TEST_SESSION_FILE}  `,
          config: compactionConfig("await"),
        }),
      );

      expect(result.ok).toBe(true);
      expect(listener).toHaveBeenCalledTimes(1);
      expect(listener).toHaveBeenCalledWith({ sessionFile: TEST_SESSION_FILE });
      expect(sync).toHaveBeenCalledWith({
        reason: "post-compaction",
        sessionFiles: [TEST_SESSION_FILE],
      });
    } finally {
      cleanup();
    }
  });

  it("runs maintain after successful compaction with a transcript rewrite helper", async () => {
    const maintain = vi.fn(async (_params?: unknown) => ({
      changed: false,
      bytesFreed: 0,
      rewrittenEntries: 0,
    }));
    resolveContextEngineMock.mockResolvedValue({
      info: { ownsCompaction: true },
      compact: contextEngineCompactMock,
      maintain,
    } as never);

    const result = await compactEmbeddedPiSession(wrappedCompactionArgs());

    expect(result.ok).toBe(true);
    expect(maintain).toHaveBeenCalledWith(
      expect.objectContaining({
        sessionKey: TEST_SESSION_KEY,
        sessionFile: TEST_SESSION_FILE,
        runtimeContext: expect.objectContaining({
          workspaceDir: TEST_WORKSPACE_DIR,
        }),
      }),
    );
    const runtimeContext = (
      maintain.mock.calls[0]?.[0] as { runtimeContext?: Record<string, unknown> } | undefined
    )?.runtimeContext;
    expect(typeof runtimeContext?.rewriteTranscriptEntries).toBe("function");
  });

  it("resolves the effective compaction model before manual engine-owned compaction", async () => {
    await compactEmbeddedPiSession(
      wrappedCompactionArgs({
        config: {
          agents: {
            defaults: {
              compaction: {
                model: "anthropic/claude-opus-4-6",
              },
            },
          },
        },
        provider: "openai-codex",
        model: "gpt-5.4",
        authProfileId: "openai:p1",
      }),
    );

    expect(resolveModelMock).toHaveBeenCalledWith(
      "anthropic",
      "claude-opus-4-6",
      expect.any(String),
      expect.anything(),
    );
    expect(contextEngineCompactMock).toHaveBeenCalledWith(
      expect.objectContaining({
        runtimeContext: expect.objectContaining({
          provider: "anthropic",
          model: "claude-opus-4-6",
          authProfileId: undefined,
        }),
      }),
    );
  });

  it("passes resolved context-engine runtime context to harness compaction", async () => {
    maybeCompactAgentHarnessSessionMock.mockResolvedValueOnce({
      ok: true,
      compacted: true,
      result: {
        summary: "harness",
        firstKeptEntryId: "entry-1",
        tokensBefore: 100,
      },
    });

    const result = await compactEmbeddedPiSession(
      wrappedCompactionArgs({
        provider: "openai-codex",
        model: "gpt-5.4",
        authProfileId: "openai:p1",
        currentTokenCount: 333,
      }),
    );

    expect(result.ok).toBe(true);
    expect(maybeCompactAgentHarnessSessionMock).toHaveBeenCalledWith(
      expect.objectContaining({
        contextEngine: expect.anything(),
        contextTokenBudget: expect.any(Number),
        contextEngineRuntimeContext: expect.objectContaining({
          sessionKey: TEST_SESSION_KEY,
          workspaceDir: TEST_WORKSPACE_DIR,
          provider: "openai-codex",
          model: "gpt-5.4",
          authProfileId: "openai:p1",
          currentTokenCount: 333,
        }),
      }),
    );
  });

  it("does not fire after_compaction when compaction fails", async () => {
    hookRunner.hasHooks.mockReturnValue(true);
    const sync = vi.fn(async () => {});
    getMemorySearchManagerMock.mockResolvedValue({ manager: { sync } });
    contextEngineCompactMock.mockResolvedValue({
      ok: false,
      compacted: false,
      reason: "nothing to compact",
      result: undefined,
    });

    const result = await compactEmbeddedPiSession(wrappedCompactionArgs());

    expect(result.ok).toBe(false);
    expect(hookRunner.runBeforeCompaction).toHaveBeenCalled();
    expect(hookRunner.runAfterCompaction).not.toHaveBeenCalled();
    expect(sync).not.toHaveBeenCalled();
  });

  it("does not duplicate transcript updates or sync in the wrapper when the engine delegates compaction", async () => {
    const listener = vi.fn();
    const cleanup = onSessionTranscriptUpdate(listener);
    const sync = vi.fn(async () => {});
    getMemorySearchManagerMock.mockResolvedValue({ manager: { sync } });
    resolveContextEngineMock.mockResolvedValue({
      info: { ownsCompaction: false },
      compact: contextEngineCompactMock,
    });

    try {
      const result = await compactEmbeddedPiSession(
        wrappedCompactionArgs({
          config: compactionConfig("await"),
        }),
      );

      expect(result.ok).toBe(true);
      expect(listener).not.toHaveBeenCalled();
      expect(sync).not.toHaveBeenCalled();
    } finally {
      cleanup();
    }
  });

  it("catches and logs hook exceptions without aborting compaction", async () => {
    hookRunner.hasHooks.mockReturnValue(true);
    hookRunner.runBeforeCompaction.mockRejectedValue(new Error("hook boom"));

    const result = await compactEmbeddedPiSession(wrappedCompactionArgs());

    expect(result.ok).toBe(true);
    expect(result.compacted).toBe(true);
    expect(contextEngineCompactMock).toHaveBeenCalled();
  });
});

¤ Dauer der Verarbeitung: 0.2 Sekunden  (vorverarbeitet am  2026-04-27) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.