From 9876a921c19332eb0665e7111d0be647bec900b9 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 8 May 2026 23:40:52 +0100 Subject: [PATCH] fix: satisfy rebased CI guardrails --- extensions/acpx/src/process-lease.test.ts | 2 +- .../auth-profile-runtime-contract.test.ts | 8 +- .../run-attempt.context-engine.test.ts | 8 +- .../codex/src/app-server/run-attempt.test.ts | 3 +- .../codex/src/app-server/trajectory.test.ts | 8 +- .../src/app-server/transcript-mirror.test.ts | 10 +- .../native-command.think-autocomplete.test.ts | 8 +- .../src/matrix/sdk/idb-persistence.test.ts | 4 +- .../memory-core/src/memory/index.test.ts | 8 +- .../src/memory/qmd-manager.test.ts | 8 +- .../scenario-runtime-e2ee-destructive.ts | 2 +- extensions/qqbot/src/state-migrations.test.ts | 6 +- .../src/host/openclaw-runtime-session.ts | 2 + .../src/host/session-transcripts.test.ts | 2 +- src/agents/agent-extension-public-types.ts | 122 ++++++++++ ...compaction.identifier-preservation.test.ts | 2 +- src/agents/compaction.retry.test.ts | 2 +- .../compaction.summarize-fallback.test.ts | 2 +- ...enclaw-owned-tool-runtime-contract.test.ts | 2 +- .../pi-embedded-runner/system-prompt.test.ts | 2 +- .../pi-hooks/compaction-safeguard.test.ts | 2 +- src/agents/pi-hooks/context-pruning.test.ts | 2 +- .../pi-hooks/context-pruning/pruner.test.ts | 2 +- src/agents/transcript/session-manager.ts | 4 +- .../transcript/session-transcript-contract.ts | 214 +++--------------- .../transcript/session-transcript-format.ts | 2 +- .../transcript/session-transcript-types.ts | 174 ++++++++++++++ src/agents/transcript/transcript-state.ts | 14 +- src/config/sessions/types.ts | 2 +- src/plugin-sdk/agent-harness-runtime.ts | 1 + src/plugin-sdk/session-store-runtime.ts | 14 +- src/plugin-sdk/test-env.ts | 2 + src/plugins/installed-plugin-index-types.ts | 10 +- 33 files changed, 417 insertions(+), 237 deletions(-) create mode 100644 src/agents/agent-extension-public-types.ts create mode 100644 src/agents/transcript/session-transcript-types.ts diff --git a/extensions/acpx/src/process-lease.test.ts b/extensions/acpx/src/process-lease.test.ts index 2ce05f2c782..84b575dbe4d 100644 --- a/extensions/acpx/src/process-lease.test.ts +++ b/extensions/acpx/src/process-lease.test.ts @@ -1,6 +1,6 @@ import { resetPluginStateStoreForTests } from "openclaw/plugin-sdk/plugin-state-runtime"; +import { withOpenClawTestState } from "openclaw/plugin-sdk/test-env"; import { afterEach, describe, expect, it } from "vitest"; -import { withOpenClawTestState } from "../../../src/test-utils/openclaw-test-state.js"; import { createAcpxProcessLeaseStore, type AcpxProcessLease } from "./process-lease.js"; function makeLease(index: number): AcpxProcessLease { diff --git a/extensions/codex/src/app-server/auth-profile-runtime-contract.test.ts b/extensions/codex/src/app-server/auth-profile-runtime-contract.test.ts index 22e42da45a7..fb5380d7cf8 100644 --- a/extensions/codex/src/app-server/auth-profile-runtime-contract.test.ts +++ b/extensions/codex/src/app-server/auth-profile-runtime-contract.test.ts @@ -6,10 +6,12 @@ import { type EmbeddedRunAttemptParams, } from "openclaw/plugin-sdk/agent-harness"; import { AUTH_PROFILE_RUNTIME_CONTRACT } from "openclaw/plugin-sdk/agent-runtime-test-contracts"; -import { createSqliteSessionTranscriptLocator } from "openclaw/plugin-sdk/session-store-runtime"; +import { + closeOpenClawAgentDatabasesForTest, + closeOpenClawStateDatabaseForTest, + createSqliteSessionTranscriptLocator, +} from "openclaw/plugin-sdk/session-store-runtime"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { closeOpenClawAgentDatabasesForTest } from "../../../../src/state/openclaw-agent-db.js"; -import { closeOpenClawStateDatabaseForTest } from "../../../../src/state/openclaw-state-db.js"; import { runCodexAppServerAttempt, __testing } from "./run-attempt.js"; import { readCodexAppServerBinding, writeCodexAppServerBinding } from "./session-binding.js"; import { createCodexTestModel } from "./test-support.js"; diff --git a/extensions/codex/src/app-server/run-attempt.context-engine.test.ts b/extensions/codex/src/app-server/run-attempt.context-engine.test.ts index e7b211f5bb9..f28c6554074 100644 --- a/extensions/codex/src/app-server/run-attempt.context-engine.test.ts +++ b/extensions/codex/src/app-server/run-attempt.context-engine.test.ts @@ -9,10 +9,12 @@ import { SessionManager, type HarnessContextEngine as ContextEngine, } from "openclaw/plugin-sdk/agent-harness-runtime"; +import { + closeOpenClawAgentDatabasesForTest, + closeOpenClawStateDatabaseForTest, + replaceSqliteSessionTranscriptEvents, +} from "openclaw/plugin-sdk/session-store-runtime"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { replaceSqliteSessionTranscriptEvents } from "../../../../src/config/sessions/transcript-store.sqlite.js"; -import { closeOpenClawAgentDatabasesForTest } from "../../../../src/state/openclaw-agent-db.js"; -import { closeOpenClawStateDatabaseForTest } from "../../../../src/state/openclaw-state-db.js"; import type { CodexServerNotification } from "./protocol.js"; import { runCodexAppServerAttempt, __testing } from "./run-attempt.js"; import { createCodexTestModel } from "./test-support.js"; diff --git a/extensions/codex/src/app-server/run-attempt.test.ts b/extensions/codex/src/app-server/run-attempt.test.ts index 95027b907d2..c67f2d10799 100644 --- a/extensions/codex/src/app-server/run-attempt.test.ts +++ b/extensions/codex/src/app-server/run-attempt.test.ts @@ -16,6 +16,7 @@ import { resetGlobalHookRunner, } from "openclaw/plugin-sdk/hook-runtime"; import { createMockPluginRegistry } from "openclaw/plugin-sdk/plugin-test-runtime"; +import { replaceSqliteSessionTranscriptEvents } from "openclaw/plugin-sdk/session-store-runtime"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; function queueActiveRunMessageForTest( @@ -23,7 +24,7 @@ function queueActiveRunMessageForTest( ): boolean { return queueAgentHarnessMessage(...args); } -import { replaceSqliteSessionTranscriptEvents } from "../../../../src/config/sessions/transcript-store.sqlite.js"; + import { CODEX_GPT5_BEHAVIOR_CONTRACT } from "../../prompt-overlay.js"; import { buildCodexAppInventoryCacheKey, diff --git a/extensions/codex/src/app-server/trajectory.test.ts b/extensions/codex/src/app-server/trajectory.test.ts index 77e509d22b9..ac070fd0536 100644 --- a/extensions/codex/src/app-server/trajectory.test.ts +++ b/extensions/codex/src/app-server/trajectory.test.ts @@ -1,10 +1,12 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; +import { listTrajectoryRuntimeEvents } from "openclaw/plugin-sdk/agent-harness-runtime"; +import { + closeOpenClawAgentDatabasesForTest, + closeOpenClawStateDatabaseForTest, +} from "openclaw/plugin-sdk/session-store-runtime"; import { afterEach, describe, expect, it } from "vitest"; -import { closeOpenClawAgentDatabasesForTest } from "../../../../src/state/openclaw-agent-db.js"; -import { closeOpenClawStateDatabaseForTest } from "../../../../src/state/openclaw-state-db.js"; -import { listTrajectoryRuntimeEvents } from "../../../../src/trajectory/runtime-store.sqlite.js"; import { createCodexTrajectoryRecorder } from "./trajectory.js"; type CodexTrajectoryRecorder = NonNullable>; diff --git a/extensions/codex/src/app-server/transcript-mirror.test.ts b/extensions/codex/src/app-server/transcript-mirror.test.ts index 2ee7b913aa7..7c3ceb9e9af 100644 --- a/extensions/codex/src/app-server/transcript-mirror.test.ts +++ b/extensions/codex/src/app-server/transcript-mirror.test.ts @@ -8,17 +8,17 @@ import { resetGlobalHookRunner, } from "openclaw/plugin-sdk/hook-runtime"; import { createMockPluginRegistry } from "openclaw/plugin-sdk/plugin-test-runtime"; +import { + closeOpenClawStateDatabaseForTest, + loadSqliteSessionTranscriptEvents, + replaceSqliteSessionTranscriptEvents, +} from "openclaw/plugin-sdk/session-store-runtime"; import { castAgentMessage, makeAgentAssistantMessage, makeAgentUserMessage, } from "openclaw/plugin-sdk/test-fixtures"; import { afterEach, describe, expect, it, vi } from "vitest"; -import { - loadSqliteSessionTranscriptEvents, - replaceSqliteSessionTranscriptEvents, -} from "../../../../src/config/sessions/transcript-store.sqlite.js"; -import { closeOpenClawStateDatabaseForTest } from "../../../../src/state/openclaw-state-db.js"; import { attachCodexMirrorIdentity, mirrorCodexAppServerTranscript } from "./transcript-mirror.js"; type MirroredAgentMessage = Extract; diff --git a/extensions/discord/src/monitor/native-command.think-autocomplete.test.ts b/extensions/discord/src/monitor/native-command.think-autocomplete.test.ts index f2cb718fd7b..27ac3fa5d7b 100644 --- a/extensions/discord/src/monitor/native-command.think-autocomplete.test.ts +++ b/extensions/discord/src/monitor/native-command.think-autocomplete.test.ts @@ -6,10 +6,12 @@ import { createEmptyPluginRegistry, setActivePluginRegistry, } from "openclaw/plugin-sdk/plugin-test-runtime"; -import { upsertSessionEntry } from "openclaw/plugin-sdk/session-store-runtime"; +import { + closeOpenClawAgentDatabasesForTest, + closeOpenClawStateDatabaseForTest, + upsertSessionEntry, +} from "openclaw/plugin-sdk/session-store-runtime"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { closeOpenClawAgentDatabasesForTest } from "../../../../src/state/openclaw-agent-db.js"; -import { closeOpenClawStateDatabaseForTest } from "../../../../src/state/openclaw-state-db.js"; import { ChannelType, type AutocompleteInteraction } from "../internal/discord.js"; import { createNoopThreadBindingManager } from "./thread-bindings.js"; diff --git a/extensions/matrix/src/matrix/sdk/idb-persistence.test.ts b/extensions/matrix/src/matrix/sdk/idb-persistence.test.ts index d24421d57a3..e9513408d62 100644 --- a/extensions/matrix/src/matrix/sdk/idb-persistence.test.ts +++ b/extensions/matrix/src/matrix/sdk/idb-persistence.test.ts @@ -89,7 +89,7 @@ describe("Matrix IndexedDB persistence", () => { it("returns false and logs a warning for malformed snapshots", async () => { const snapshot = snapshotPath("bad-snapshot.json"); - const store = createPluginBlobStore>("matrix", { + const store = createPluginBlobStore("matrix", { namespace: MATRIX_IDB_SNAPSHOT_NAMESPACE, maxEntries: 1_000, }); @@ -110,7 +110,7 @@ describe("Matrix IndexedDB persistence", () => { it("returns false for empty snapshot payloads without restoring databases", async () => { const snapshot = snapshotPath("empty-snapshot.json"); - const store = createPluginBlobStore>("matrix", { + const store = createPluginBlobStore("matrix", { namespace: MATRIX_IDB_SNAPSHOT_NAMESPACE, maxEntries: 1_000, }); diff --git a/extensions/memory-core/src/memory/index.test.ts b/extensions/memory-core/src/memory/index.test.ts index f58acc9041b..69c4759c0dc 100644 --- a/extensions/memory-core/src/memory/index.test.ts +++ b/extensions/memory-core/src/memory/index.test.ts @@ -7,14 +7,14 @@ import { listRegisteredMemoryEmbeddingProviderAdapters as listRegisteredAdapters, registerMemoryEmbeddingProvider as registerAdapter, } from "openclaw/plugin-sdk/memory-core-host-engine-embeddings"; -import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { replaceSqliteSessionTranscriptEvents } from "../../../../src/config/sessions/transcript-store.sqlite.js"; import { closeOpenClawAgentDatabasesForTest, + closeOpenClawStateDatabaseForTest, openOpenClawAgentDatabase, + replaceSqliteSessionTranscriptEvents, resolveOpenClawAgentSqlitePath, -} from "../../../../src/state/openclaw-agent-db.js"; -import { closeOpenClawStateDatabaseForTest } from "../../../../src/state/openclaw-state-db.js"; +} from "openclaw/plugin-sdk/session-store-runtime"; +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import "./test-runtime-mocks.js"; import type { MemoryIndexManager } from "./index.js"; import { closeAllMemorySearchManagers, getMemorySearchManager } from "./index.js"; diff --git a/extensions/memory-core/src/memory/qmd-manager.test.ts b/extensions/memory-core/src/memory/qmd-manager.test.ts index 41be1a58b4b..80466c0b5ad 100644 --- a/extensions/memory-core/src/memory/qmd-manager.test.ts +++ b/extensions/memory-core/src/memory/qmd-manager.test.ts @@ -4,11 +4,13 @@ import os from "node:os"; import path from "node:path"; import type { DatabaseSync } from "node:sqlite"; import { setTimeout as scheduleNativeTimeout } from "node:timers"; +import { + closeOpenClawAgentDatabasesForTest, + closeOpenClawStateDatabaseForTest, + replaceSqliteSessionTranscriptEvents, +} from "openclaw/plugin-sdk/session-store-runtime"; import type { Mock } from "vitest"; import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { replaceSqliteSessionTranscriptEvents } from "../../../../src/config/sessions/transcript-store.sqlite.js"; -import { closeOpenClawAgentDatabasesForTest } from "../../../../src/state/openclaw-agent-db.js"; -import { closeOpenClawStateDatabaseForTest } from "../../../../src/state/openclaw-state-db.js"; const { logWarnMock, logDebugMock, logInfoMock } = vi.hoisted(() => ({ logWarnMock: vi.fn(), diff --git a/extensions/qa-matrix/src/runners/contract/scenario-runtime-e2ee-destructive.ts b/extensions/qa-matrix/src/runners/contract/scenario-runtime-e2ee-destructive.ts index 043a95ba751..1efe1ceebc5 100644 --- a/extensions/qa-matrix/src/runners/contract/scenario-runtime-e2ee-destructive.ts +++ b/extensions/qa-matrix/src/runners/contract/scenario-runtime-e2ee-destructive.ts @@ -56,7 +56,7 @@ const matrixStorageMetaStore = createPluginStateKeyedStore>("matrix", { +const matrixIdbSnapshotStore = createPluginBlobStore("matrix", { namespace: MATRIX_IDB_SNAPSHOT_NAMESPACE, maxEntries: 1_000, }); diff --git a/extensions/qqbot/src/state-migrations.test.ts b/extensions/qqbot/src/state-migrations.test.ts index 64f66760136..02b3781e359 100644 --- a/extensions/qqbot/src/state-migrations.test.ts +++ b/extensions/qqbot/src/state-migrations.test.ts @@ -1,11 +1,11 @@ import fs from "node:fs/promises"; import path from "node:path"; -import { afterEach, describe, expect, it } from "vitest"; import { closeOpenClawStateDatabaseForTest, openOpenClawStateDatabase, -} from "../../../src/state/openclaw-state-db.js"; -import { createTrackedTempDirs } from "../../../src/test-utils/tracked-temp-dirs.js"; +} from "openclaw/plugin-sdk/session-store-runtime"; +import { createTrackedTempDirs } from "openclaw/plugin-sdk/test-env"; +import { afterEach, describe, expect, it } from "vitest"; import { detectQQBotLegacyStateMigrations } from "./state-migrations.js"; const tempDirs = createTrackedTempDirs(); diff --git a/packages/memory-host-sdk/src/host/openclaw-runtime-session.ts b/packages/memory-host-sdk/src/host/openclaw-runtime-session.ts index 472dd18a5ec..1147f43280c 100644 --- a/packages/memory-host-sdk/src/host/openclaw-runtime-session.ts +++ b/packages/memory-host-sdk/src/host/openclaw-runtime-session.ts @@ -13,6 +13,8 @@ export { loadSqliteSessionTranscriptEvents, onSessionTranscriptUpdate, parseUsageCountedSessionIdFromFileName, + closeOpenClawStateDatabaseForTest, + replaceSqliteSessionTranscriptEvents, resolveSqliteSessionTranscriptScopeForPath, stripInboundMetadata, stripInternalRuntimeContext, diff --git a/packages/memory-host-sdk/src/host/session-transcripts.test.ts b/packages/memory-host-sdk/src/host/session-transcripts.test.ts index c513331c6bb..7a5d13c9d47 100644 --- a/packages/memory-host-sdk/src/host/session-transcripts.test.ts +++ b/packages/memory-host-sdk/src/host/session-transcripts.test.ts @@ -5,7 +5,7 @@ import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from import { closeOpenClawStateDatabaseForTest, replaceSqliteSessionTranscriptEvents, -} from "./openclaw-runtime.js"; +} from "./openclaw-runtime-session.js"; import { buildSessionTranscriptEntry, listSessionTranscriptsForAgent, diff --git a/src/agents/agent-extension-public-types.ts b/src/agents/agent-extension-public-types.ts new file mode 100644 index 00000000000..6892d414369 --- /dev/null +++ b/src/agents/agent-extension-public-types.ts @@ -0,0 +1,122 @@ +import type { AgentMessage, AgentToolResult } from "./agent-core-contract.js"; +import type { Api, Model } from "./pi-ai-contract.js"; + +export type AgentSessionEventListener = { + bivarianceHack(event: TEvent): void; +}["bivarianceHack"]; + +export type AgentSession = { + agent: { + state: { + systemPrompt: string; + }; + }; + messages: AgentMessage[]; + sessionFile?: string; + isCompacting: boolean; + subscribe(listener: AgentSessionEventListener): () => void; + abortCompaction(): void; + setActiveToolsByName(toolNames: string[]): void; +}; + +export type FileOperations = { + read: Iterable; + written: Iterable; + edited: Iterable; +}; + +export type ContextUsage = { + tokens: number | null; + contextWindow: number; + percent: number | null; +}; + +export type CompactOptions = { + customInstructions?: string; + onComplete?: (result: { summary: string }) => void; + onError?: (error: Error) => void; +}; + +export type ExtensionContext = { + cwd: string; + sessionManager: object; + modelRegistry: unknown; + model: Model | undefined; + isIdle(): boolean; + signal: AbortSignal | undefined; + abort(): void; + hasPendingMessages(): boolean; + shutdown(): void; + getContextUsage(): ContextUsage | undefined; + compact(options?: CompactOptions): void; + getSystemPrompt(): string; +}; + +export type ContextEvent = { + type: "context"; + messages: AgentMessage[]; +}; + +export type ContextEventResult = { + messages?: AgentMessage[]; +}; + +export type CompactionPreparation = { + messagesToSummarize: AgentMessage[]; + turnPrefixMessages?: AgentMessage[]; + previousSummary?: string; + firstKeptEntryId: string; + tokensBefore: number; + fileOps: FileOperations; + isSplitTurn?: boolean; + settings: { + reserveTokens: number; + }; +}; + +export type SessionBeforeCompactEvent = { + type: "session_before_compact"; + preparation: CompactionPreparation; + customInstructions?: string; + signal: AbortSignal; +}; + +export type SessionBeforeCompactResult = { + cancel?: boolean; + compaction?: { + summary: string; + firstKeptEntryId: string; + tokensBefore: number; + details?: unknown; + }; +}; + +export type ToolResultEvent = { + type: "tool_result"; + toolCallId: string; + toolName: string; + input: Record; + content: AgentToolResult["content"]; + details?: unknown; + isError: boolean; +}; + +export type ToolResultEventResult = { + content?: AgentToolResult["content"]; + details?: unknown; + isError?: boolean; +}; + +export type ExtensionHandler = ( + event: E, + ctx: ExtensionContext, +) => Promise | R | void; + +export type ExtensionAPI = { + on(event: "context", handler: ExtensionHandler): void; + on( + event: "session_before_compact", + handler: ExtensionHandler, + ): void; + on(event: "tool_result", handler: ExtensionHandler): void; +}; diff --git a/src/agents/compaction.identifier-preservation.test.ts b/src/agents/compaction.identifier-preservation.test.ts index 44eadf7940f..d9ffef5bd66 100644 --- a/src/agents/compaction.identifier-preservation.test.ts +++ b/src/agents/compaction.identifier-preservation.test.ts @@ -1,7 +1,7 @@ import type { AgentMessage } from "openclaw/plugin-sdk/agent-core"; import { beforeEach, describe, expect, it, vi } from "vitest"; +import type { ExtensionContext } from "./agent-extension-contract.js"; import * as piCodingAgent from "./pi-coding-agent-contract.js"; -import type { ExtensionContext } from "./transcript/session-transcript-contract.js"; vi.mock("./pi-coding-agent-contract.js", async () => { const actual = await vi.importActual("./pi-coding-agent-contract.js"); diff --git a/src/agents/compaction.retry.test.ts b/src/agents/compaction.retry.test.ts index fdad739b8be..618e93b6e58 100644 --- a/src/agents/compaction.retry.test.ts +++ b/src/agents/compaction.retry.test.ts @@ -1,9 +1,9 @@ import type { AgentMessage } from "openclaw/plugin-sdk/agent-core"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { retryAsync } from "../infra/retry.js"; +import type { ExtensionContext } from "./agent-extension-contract.js"; import type { AssistantMessage, UserMessage } from "./pi-ai-contract.js"; import * as piCodingAgent from "./pi-coding-agent-contract.js"; -import type { ExtensionContext } from "./transcript/session-transcript-contract.js"; // Mock the external generateSummary function vi.mock("./pi-coding-agent-contract.js", async () => { diff --git a/src/agents/compaction.summarize-fallback.test.ts b/src/agents/compaction.summarize-fallback.test.ts index c4837996e1c..d7ef8ebe3c9 100644 --- a/src/agents/compaction.summarize-fallback.test.ts +++ b/src/agents/compaction.summarize-fallback.test.ts @@ -1,8 +1,8 @@ import type { AgentMessage } from "openclaw/plugin-sdk/agent-core"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { summarizeWithFallback } from "./compaction.js"; +import type { ExtensionContext } from "./agent-extension-contract.js"; import type { UserMessage } from "./pi-ai-contract.js"; -import type { ExtensionContext } from "./transcript/session-transcript-contract.js"; const piCodingAgentMocks = vi.hoisted(() => ({ generateSummary: vi.fn(), diff --git a/src/agents/openclaw-owned-tool-runtime-contract.test.ts b/src/agents/openclaw-owned-tool-runtime-contract.test.ts index 7e4b0dae4ed..b7cec2b4665 100644 --- a/src/agents/openclaw-owned-tool-runtime-contract.test.ts +++ b/src/agents/openclaw-owned-tool-runtime-contract.test.ts @@ -5,6 +5,7 @@ import { textToolResult, } from "openclaw/plugin-sdk/agent-runtime-test-contracts"; import { afterEach, describe, expect, it, vi } from "vitest"; +import type { ExtensionContext } from "./agent-extension-contract.js"; import type { MessagingToolSend } from "./pi-embedded-messaging.types.js"; import { handleToolExecutionEnd, @@ -17,7 +18,6 @@ import type { import { toToolDefinitions } from "./pi-tool-definition-adapter.js"; import { createBaseToolHandlerState } from "./pi-tool-handler-state.test-helpers.js"; import { wrapToolWithBeforeToolCallHook } from "./pi-tools.before-tool-call.js"; -import type { ExtensionContext } from "./transcript/session-transcript-contract.js"; function createContractTool(name: string, execute: AgentTool["execute"]): AgentTool { return { diff --git a/src/agents/pi-embedded-runner/system-prompt.test.ts b/src/agents/pi-embedded-runner/system-prompt.test.ts index aaebaee5ae7..78a9069b514 100644 --- a/src/agents/pi-embedded-runner/system-prompt.test.ts +++ b/src/agents/pi-embedded-runner/system-prompt.test.ts @@ -1,6 +1,6 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { clearMemoryPluginState, registerMemoryPromptSection } from "../../plugins/memory-state.js"; -import type { AgentSession } from "../transcript/session-transcript-contract.js"; +import type { AgentSession } from "../agent-extension-contract.js"; import { applySystemPromptOverrideToSession, buildEmbeddedSystemPrompt, diff --git a/src/agents/pi-hooks/compaction-safeguard.test.ts b/src/agents/pi-hooks/compaction-safeguard.test.ts index a4cba62341d..7df8ca331c0 100644 --- a/src/agents/pi-hooks/compaction-safeguard.test.ts +++ b/src/agents/pi-hooks/compaction-safeguard.test.ts @@ -8,11 +8,11 @@ import { clearCompactionProviders, registerCompactionProvider, } from "../../plugins/compaction-provider.js"; +import type { ExtensionAPI, ExtensionContext } from "../agent-extension-contract.js"; import * as compactionModule from "../compaction.js"; import type { Api, Model } from "../pi-ai-contract.js"; import { buildEmbeddedExtensionFactories } from "../pi-embedded-runner/extensions.js"; import { castAgentMessage } from "../test-helpers/agent-message-fixtures.js"; -import type { ExtensionAPI, ExtensionContext } from "../transcript/session-transcript-contract.js"; import { consumeCompactionSafeguardCancelReason, getCompactionSafeguardRuntime, diff --git a/src/agents/pi-hooks/context-pruning.test.ts b/src/agents/pi-hooks/context-pruning.test.ts index 89fd2a96e5f..b22a4aeae4b 100644 --- a/src/agents/pi-hooks/context-pruning.test.ts +++ b/src/agents/pi-hooks/context-pruning.test.ts @@ -1,7 +1,7 @@ import type { AgentMessage } from "openclaw/plugin-sdk/agent-core"; import { describe, expect, it } from "vitest"; +import type { ExtensionAPI, ExtensionContext } from "../agent-extension-contract.js"; import type { ToolResultMessage } from "../pi-ai-contract.js"; -import type { ExtensionAPI, ExtensionContext } from "../transcript/session-transcript-contract.js"; import { computeEffectiveSettings, default as contextPruningExtension, diff --git a/src/agents/pi-hooks/context-pruning/pruner.test.ts b/src/agents/pi-hooks/context-pruning/pruner.test.ts index 99b470b00a8..28bb200549e 100644 --- a/src/agents/pi-hooks/context-pruning/pruner.test.ts +++ b/src/agents/pi-hooks/context-pruning/pruner.test.ts @@ -1,6 +1,6 @@ import type { AgentMessage } from "openclaw/plugin-sdk/agent-core"; import { describe, expect, it } from "vitest"; -import type { ExtensionContext } from "../../transcript/session-transcript-contract.js"; +import type { ExtensionContext } from "../../agent-extension-contract.js"; import { pruneContextMessages } from "./pruner.js"; import { DEFAULT_CONTEXT_PRUNING_SETTINGS } from "./settings.js"; diff --git a/src/agents/transcript/session-manager.ts b/src/agents/transcript/session-manager.ts index 4723e51a002..e50b7eb9c00 100644 --- a/src/agents/transcript/session-manager.ts +++ b/src/agents/transcript/session-manager.ts @@ -13,6 +13,7 @@ import { resolveSqliteSessionTranscriptScopeForPath, } from "../../config/sessions/transcript-store.sqlite.js"; import { DEFAULT_AGENT_ID } from "../../routing/session-key.js"; +import { CURRENT_SESSION_VERSION } from "./session-transcript-format.js"; import type { FileEntry, SessionContext, @@ -22,8 +23,7 @@ import type { SessionListProgress, SessionManager, SessionTreeNode, -} from "./session-transcript-contract.js"; -import { CURRENT_SESSION_VERSION } from "./session-transcript-format.js"; +} from "./session-transcript-types.js"; import { TranscriptState } from "./transcript-state.js"; function createSessionHeader(params: { diff --git a/src/agents/transcript/session-transcript-contract.ts b/src/agents/transcript/session-transcript-contract.ts index 5dfefd261ee..539366f8325 100644 --- a/src/agents/transcript/session-transcript-contract.ts +++ b/src/agents/transcript/session-transcript-contract.ts @@ -1,192 +1,48 @@ -import type { AgentMessage } from "../agent-core-contract.js"; -import type { ImageContent, TextContent } from "../pi-ai-contract.js"; import { SessionManagerValue } from "./session-manager.js"; +import type { + SessionInfo, + SessionListProgress, + SessionManager as SessionManagerType, +} from "./session-transcript-types.js"; export { buildSessionContext, CURRENT_SESSION_VERSION, migrateSessionEntries, parseSessionEntries, } from "./session-transcript-format.js"; -export type { AgentSession, ExtensionAPI, ExtensionContext } from "../agent-extension-contract.js"; +export type { + AgentSession, + ExtensionAPI, + ExtensionContext, +} from "../agent-extension-public-types.js"; +export type { + BranchSummaryEntry, + CompactionEntry, + CustomEntry, + CustomMessageEntry, + FileEntry, + LabelEntry, + ModelChangeEntry, + SessionContext, + SessionEntry, + SessionEntryBase, + SessionHeader, + SessionInfo, + SessionInfoEntry, + SessionListProgress, + SessionMessageEntry, + SessionTreeNode, + ThinkingLevelChangeEntry, +} from "./session-transcript-types.js"; -export type SessionHeader = { - type: "session"; - version?: number; - id: string; - timestamp: string; - cwd: string; - parentSession?: string; -}; - -export type SessionEntryBase = { - type: string; - id: string; - parentId: string | null; - timestamp: string; -}; - -export type SessionMessageEntry = SessionEntryBase & { - type: "message"; - message: AgentMessage; -}; - -export type ThinkingLevelChangeEntry = SessionEntryBase & { - type: "thinking_level_change"; - thinkingLevel: string; -}; - -export type ModelChangeEntry = SessionEntryBase & { - type: "model_change"; - provider: string; - modelId: string; -}; - -export type CompactionEntry = SessionEntryBase & { - type: "compaction"; - summary: string; - firstKeptEntryId: string; - tokensBefore: number; - details?: T; - fromHook?: boolean; -}; - -export type BranchSummaryEntry = SessionEntryBase & { - type: "branch_summary"; - fromId: string; - summary: string; - details?: T; - fromHook?: boolean; -}; - -export type CustomEntry = SessionEntryBase & { - type: "custom"; - customType: string; - data?: T; -}; - -export type LabelEntry = SessionEntryBase & { - type: "label"; - targetId: string; - label: string | undefined; -}; - -export type SessionInfoEntry = SessionEntryBase & { - type: "session_info"; - name?: string; -}; - -export type CustomMessageEntry = SessionEntryBase & { - type: "custom_message"; - customType: string; - content: string | (TextContent | ImageContent)[]; - details?: T; - display: boolean; -}; - -export type SessionEntry = - | SessionMessageEntry - | ThinkingLevelChangeEntry - | ModelChangeEntry - | CompactionEntry - | BranchSummaryEntry - | CustomEntry - | CustomMessageEntry - | LabelEntry - | SessionInfoEntry; - -export type FileEntry = SessionHeader | SessionEntry; - -export type SessionTreeNode = { - entry: SessionEntry; - children: SessionTreeNode[]; - label?: string; - labelTimestamp?: string; -}; - -export type SessionContext = { - messages: AgentMessage[]; - thinkingLevel: string; - model: { provider: string; modelId: string } | null; -}; - -export type SessionInfo = { - path: string; - id: string; - cwd: string; - name?: string; - parentSessionPath?: string; - created: Date; - modified: Date; - messageCount: number; - firstMessage: string; - allMessagesText: string; -}; - -export type SessionListProgress = (loaded: number, total: number) => void; - -type PersistableSessionMessage = Exclude< - AgentMessage, - { role: "branchSummary" | "compactionSummary" } ->; - -export type SessionManager = { - setSessionFile(sessionFile: string): void; - newSession(options?: { id?: string; parentSession?: string }): string | undefined; - isPersisted(): boolean; - getCwd(): string; - getSessionId(): string; - getSessionFile(): string | undefined; - appendMessage(message: PersistableSessionMessage): string; - appendThinkingLevelChange(thinkingLevel: string): string; - appendModelChange(provider: string, modelId: string): string; - appendCompaction( - summary: string, - firstKeptEntryId: string, - tokensBefore: number, - details?: unknown, - fromHook?: boolean, - ): string; - appendCustomEntry(customType: string, data?: unknown): string; - appendSessionInfo(name: string): string; - getSessionName(): string | undefined; - appendCustomMessageEntry( - customType: string, - content: string | (TextContent | ImageContent)[], - display: boolean, - details?: unknown, - ): string; - getLeafId(): string | null; - getLeafEntry(): SessionEntry | undefined; - getEntry(id: string): SessionEntry | undefined; - getChildren(parentId: string): SessionEntry[]; - getLabel(id: string): string | undefined; - appendLabelChange(targetId: string, label: string | undefined): string; - getBranch(fromId?: string): SessionEntry[]; - buildSessionContext(): SessionContext; - getHeader(): SessionHeader | null; - getEntries(): SessionEntry[]; - getTree(): SessionTreeNode[]; - branch(branchFromId: string): void; - resetLeaf(): void; - removeTailEntries( - shouldRemove: (entry: SessionEntry) => boolean, - options?: { maxEntries?: number; minEntries?: number }, - ): number; - branchWithSummary( - branchFromId: string | null, - summary: string, - details?: unknown, - fromHook?: boolean, - ): string; - createBranchedSession(leafId: string): string | undefined; -}; +export type SessionManager = SessionManagerType; export const SessionManager = SessionManagerValue as { - create(cwd: string): SessionManager; - open(path: string, cwdOverride?: string): SessionManager; - continueRecent(cwd: string): SessionManager; - inMemory(cwd?: string): SessionManager; - forkFrom(sourcePath: string, targetCwd: string): SessionManager; + create(cwd: string): SessionManagerType; + open(path: string, cwdOverride?: string): SessionManagerType; + continueRecent(cwd: string): SessionManagerType; + inMemory(cwd?: string): SessionManagerType; + forkFrom(sourcePath: string, targetCwd: string): SessionManagerType; list(cwd: string, onProgress?: SessionListProgress): Promise; listAll(onProgress?: SessionListProgress): Promise; }; diff --git a/src/agents/transcript/session-transcript-format.ts b/src/agents/transcript/session-transcript-format.ts index d9334fb80f8..198b51ffa68 100644 --- a/src/agents/transcript/session-transcript-format.ts +++ b/src/agents/transcript/session-transcript-format.ts @@ -8,7 +8,7 @@ import type { SessionContext, SessionEntry, SessionHeader, -} from "./session-transcript-contract.js"; +} from "./session-transcript-types.js"; export const CURRENT_SESSION_VERSION = 3; diff --git a/src/agents/transcript/session-transcript-types.ts b/src/agents/transcript/session-transcript-types.ts new file mode 100644 index 00000000000..371f2f96738 --- /dev/null +++ b/src/agents/transcript/session-transcript-types.ts @@ -0,0 +1,174 @@ +import type { AgentMessage } from "../agent-core-contract.js"; +import type { ImageContent, TextContent } from "../pi-ai-contract.js"; + +export type SessionHeader = { + type: "session"; + version?: number; + id: string; + timestamp: string; + cwd: string; + parentSession?: string; +}; + +export type SessionEntryBase = { + type: string; + id: string; + parentId: string | null; + timestamp: string; +}; + +export type SessionMessageEntry = SessionEntryBase & { + type: "message"; + message: AgentMessage; +}; + +export type ThinkingLevelChangeEntry = SessionEntryBase & { + type: "thinking_level_change"; + thinkingLevel: string; +}; + +export type ModelChangeEntry = SessionEntryBase & { + type: "model_change"; + provider: string; + modelId: string; +}; + +export type CompactionEntry = SessionEntryBase & { + type: "compaction"; + summary: string; + firstKeptEntryId: string; + tokensBefore: number; + details?: T; + fromHook?: boolean; +}; + +export type BranchSummaryEntry = SessionEntryBase & { + type: "branch_summary"; + fromId: string; + summary: string; + details?: T; + fromHook?: boolean; +}; + +export type CustomEntry = SessionEntryBase & { + type: "custom"; + customType: string; + data?: T; +}; + +export type LabelEntry = SessionEntryBase & { + type: "label"; + targetId: string; + label: string | undefined; +}; + +export type SessionInfoEntry = SessionEntryBase & { + type: "session_info"; + name?: string; +}; + +export type CustomMessageEntry = SessionEntryBase & { + type: "custom_message"; + customType: string; + content: string | (TextContent | ImageContent)[]; + details?: T; + display: boolean; +}; + +export type SessionEntry = + | SessionMessageEntry + | ThinkingLevelChangeEntry + | ModelChangeEntry + | CompactionEntry + | BranchSummaryEntry + | CustomEntry + | CustomMessageEntry + | LabelEntry + | SessionInfoEntry; + +export type FileEntry = SessionHeader | SessionEntry; + +export type SessionTreeNode = { + entry: SessionEntry; + children: SessionTreeNode[]; + label?: string; + labelTimestamp?: string; +}; + +export type SessionContext = { + messages: AgentMessage[]; + thinkingLevel: string; + model: { provider: string; modelId: string } | null; +}; + +export type SessionInfo = { + path: string; + id: string; + cwd: string; + name?: string; + parentSessionPath?: string; + created: Date; + modified: Date; + messageCount: number; + firstMessage: string; + allMessagesText: string; +}; + +export type SessionListProgress = (loaded: number, total: number) => void; + +type PersistableSessionMessage = Exclude< + AgentMessage, + { role: "branchSummary" | "compactionSummary" } +>; + +export type SessionManager = { + setSessionFile(sessionFile: string): void; + newSession(options?: { id?: string; parentSession?: string }): string | undefined; + isPersisted(): boolean; + getCwd(): string; + getSessionId(): string; + getSessionFile(): string | undefined; + appendMessage(message: PersistableSessionMessage): string; + appendThinkingLevelChange(thinkingLevel: string): string; + appendModelChange(provider: string, modelId: string): string; + appendCompaction( + summary: string, + firstKeptEntryId: string, + tokensBefore: number, + details?: unknown, + fromHook?: boolean, + ): string; + appendCustomEntry(customType: string, data?: unknown): string; + appendSessionInfo(name: string): string; + getSessionName(): string | undefined; + appendCustomMessageEntry( + customType: string, + content: string | (TextContent | ImageContent)[], + display: boolean, + details?: unknown, + ): string; + getLeafId(): string | null; + getLeafEntry(): SessionEntry | undefined; + getEntry(id: string): SessionEntry | undefined; + getChildren(parentId: string): SessionEntry[]; + getLabel(id: string): string | undefined; + appendLabelChange(targetId: string, label: string | undefined): string; + getBranch(fromId?: string): SessionEntry[]; + buildSessionContext(): SessionContext; + getHeader(): SessionHeader | null; + getEntries(): SessionEntry[]; + getTree(): SessionTreeNode[]; + branch(branchFromId: string): void; + resetLeaf(): void; + removeTailEntries( + shouldRemove: (entry: SessionEntry) => boolean, + options?: { maxEntries?: number; minEntries?: number }, + ): number; + branchWithSummary( + branchFromId: string | null, + summary: string, + details?: unknown, + fromHook?: boolean, + ): string; + createBranchedSession(leafId: string): string | undefined; +}; diff --git a/src/agents/transcript/transcript-state.ts b/src/agents/transcript/transcript-state.ts index 212c4179610..595eb0739d9 100644 --- a/src/agents/transcript/transcript-state.ts +++ b/src/agents/transcript/transcript-state.ts @@ -1,24 +1,24 @@ import { randomUUID } from "node:crypto"; import path from "node:path"; -import { isSqliteSessionTranscriptLocator } from "../../config/sessions.js"; +import { isSqliteSessionTranscriptLocator } from "../../config/sessions/paths.js"; import { appendSqliteSessionTranscriptEvent, loadSqliteSessionTranscriptEvents, replaceSqliteSessionTranscriptEvents, resolveSqliteSessionTranscriptScopeForPath, } from "../../config/sessions/transcript-store.sqlite.js"; +import { + buildSessionContext, + CURRENT_SESSION_VERSION, + migrateSessionEntries, +} from "./session-transcript-format.js"; import type { FileEntry, SessionContext, SessionEntry, SessionHeader, SessionTreeNode, -} from "./session-transcript-contract.js"; -import { - buildSessionContext, - CURRENT_SESSION_VERSION, - migrateSessionEntries, -} from "./session-transcript-format.js"; +} from "./session-transcript-types.js"; type BranchSummaryEntry = Extract; type CompactionEntry = Extract; diff --git a/src/config/sessions/types.ts b/src/config/sessions/types.ts index 971790c4d7f..5819c40aeed 100644 --- a/src/config/sessions/types.ts +++ b/src/config/sessions/types.ts @@ -243,7 +243,7 @@ export type SessionEntry = { /** Epoch ms cutoff paired with abortCutoffMessageSid when available. */ abortCutoffTimestamp?: number; chatType?: SessionChatType; - /** Legacy alias migrated to lastChannel by doctor session migration. */ + /** @deprecated Legacy alias migrated to lastChannel by doctor session migration. */ lastProvider?: string; thinkingLevel?: string; fastMode?: boolean; diff --git a/src/plugin-sdk/agent-harness-runtime.ts b/src/plugin-sdk/agent-harness-runtime.ts index d2d64d6ada9..7a9cc01a370 100644 --- a/src/plugin-sdk/agent-harness-runtime.ts +++ b/src/plugin-sdk/agent-harness-runtime.ts @@ -43,6 +43,7 @@ export type { HeartbeatToolResponse } from "../auto-reply/heartbeat-tool-respons export type { AgentApprovalEventData, AgentEventPayload } from "../infra/agent-events.js"; export type { ExecApprovalDecision } from "../infra/exec-approvals.js"; export type { NormalizedUsage } from "../agents/usage.js"; +export { listTrajectoryRuntimeEvents } from "../trajectory/runtime-store.sqlite.js"; export type { AgentToolResultMiddleware, AgentToolResultMiddlewareContext, diff --git a/src/plugin-sdk/session-store-runtime.ts b/src/plugin-sdk/session-store-runtime.ts index 33a74cccfcf..3a19bf94ab1 100644 --- a/src/plugin-sdk/session-store-runtime.ts +++ b/src/plugin-sdk/session-store-runtime.ts @@ -1,6 +1,14 @@ // Narrow SQLite session row helpers for channel hot paths. -export { closeOpenClawAgentDatabasesForTest } from "../state/openclaw-agent-db.js"; +export { + closeOpenClawAgentDatabasesForTest, + openOpenClawAgentDatabase, + resolveOpenClawAgentSqlitePath, +} from "../state/openclaw-agent-db.js"; +export { + closeOpenClawStateDatabaseForTest, + openOpenClawStateDatabase, +} from "../state/openclaw-state-db.js"; export { resolveSessionRowEntry } from "../config/sessions/store-entry.js"; export { createSqliteSessionTranscriptLocator } from "../config/sessions/paths.js"; export { resolveAndPersistSessionTranscriptLocator } from "../config/sessions/session-locator.js"; @@ -17,6 +25,10 @@ export { updateLastRoute, upsertSessionEntry, } from "../config/sessions/store.js"; +export { + loadSqliteSessionTranscriptEvents, + replaceSqliteSessionTranscriptEvents, +} from "../config/sessions/transcript-store.sqlite.js"; export { evaluateSessionFreshness, resolveChannelResetConfig, diff --git a/src/plugin-sdk/test-env.ts b/src/plugin-sdk/test-env.ts index f743c23b668..814dcb9af30 100644 --- a/src/plugin-sdk/test-env.ts +++ b/src/plugin-sdk/test-env.ts @@ -62,6 +62,8 @@ export { createMockServerResponse } from "../test-utils/mock-http-response.js"; export { createTempHomeEnv, type TempHomeEnv } from "../test-utils/temp-home.js"; export { withTempDir } from "../test-utils/temp-dir.js"; export { useFrozenTime, useRealTime } from "../test-utils/frozen-time.js"; +export { withOpenClawTestState } from "../test-utils/openclaw-test-state.js"; +export { createTrackedTempDirs } from "../test-utils/tracked-temp-dirs.js"; export { withServer } from "./test-helpers/http-test-server.js"; export { createMockIncomingRequest } from "./test-helpers/mock-incoming-request.js"; export { withTempHome } from "./test-helpers/temp-home.js"; diff --git a/src/plugins/installed-plugin-index-types.ts b/src/plugins/installed-plugin-index-types.ts index 0270bc2b4d1..e9235c2f2b2 100644 --- a/src/plugins/installed-plugin-index-types.ts +++ b/src/plugins/installed-plugin-index-types.ts @@ -4,9 +4,9 @@ import type { PluginCompatCode } from "./compat/registry.js"; import type { PluginCandidate } from "./discovery.js"; import type { PluginInstallSourceInfo } from "./install-source-info.js"; import type { InstalledPluginFileSignature } from "./installed-plugin-index-hash.js"; -import type { PluginManifestRecord } from "./manifest-registry.js"; -import type { PluginDiagnostic } from "./manifest-types.js"; +import type { PluginDiagnostic, PluginBundleFormat, PluginFormat } from "./manifest-types.js"; import type { PluginPackageChannel } from "./manifest.js"; +import type { PluginOrigin } from "./plugin-origin.types.js"; export const INSTALLED_PLUGIN_INDEX_VERSION = 1; export const INSTALLED_PLUGIN_INDEX_MIGRATION_VERSION = 1; @@ -88,8 +88,8 @@ export type InstalledPluginIndexRecord = { manifestPath: string; manifestHash: string; manifestFile?: InstalledPluginFileSignature; - format?: PluginManifestRecord["format"]; - bundleFormat?: PluginManifestRecord["bundleFormat"]; + format?: PluginFormat; + bundleFormat?: PluginBundleFormat; source?: string; setupSource?: string; packageJson?: { @@ -98,7 +98,7 @@ export type InstalledPluginIndexRecord = { fileSignature?: InstalledPluginFileSignature; }; rootDir: string; - origin: PluginManifestRecord["origin"]; + origin: PluginOrigin; enabled: boolean; enabledByDefault?: boolean; enabledByDefaultOnPlatforms?: readonly string[];