From 880ad884a7b82d90c033ff02da2576ccc87d6275 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 9 May 2026 17:55:44 +0100 Subject: [PATCH] refactor: remove transcript locator test helper --- docs/concepts/active-memory.md | 8 +-- docs/pi.md | 9 ++- docs/plugins/sdk-runtime.md | 5 +- .../lib/codex-npm-plugin-live/assertions.mjs | 7 +-- .../e2e/lib/live-plugin-tool/assertions.mjs | 38 ++++++++--- scripts/e2e/mcp-channels-seed.ts | 7 --- .../main-session-restart-recovery.test.ts | 1 - .../attempt.spawn-workspace.test-support.ts | 15 ++--- .../test-helpers/transcript-locator.ts | 63 ------------------- .../sessions/transcript-store.sqlite.test.ts | 2 - src/config/sessions/transcript.test.ts | 1 - .../isolated-agent.session-identity.test.ts | 16 +++-- 12 files changed, 56 insertions(+), 116 deletions(-) delete mode 100644 src/config/sessions/test-helpers/transcript-locator.ts diff --git a/docs/concepts/active-memory.md b/docs/concepts/active-memory.md index 1de63c3a740..bb5d5d4e5cb 100644 --- a/docs/concepts/active-memory.md +++ b/docs/concepts/active-memory.md @@ -617,12 +617,12 @@ during the blocking memory sub-agent call. By default, that transcript is internal: -- it uses a `sqlite-transcript:///.jsonl` locator +- it is addressed by `{ agentId, sessionId }` - it is used only for the blocking memory sub-agent run -- it does not create a JSONL sidecar +- it does not create a JSONL sidecar or transcript locator -If you want the blocking memory sub-agent transcript locator logged for debugging -or inspection, turn persistence on explicitly: +If you want the blocking memory sub-agent transcript retained for debugging or +inspection, turn persistence on explicitly: ```json5 { diff --git a/docs/pi.md b/docs/pi.md index a3e371abacf..1e9755e6dc6 100644 --- a/docs/pi.md +++ b/docs/pi.md @@ -147,9 +147,9 @@ The main entry point is `runEmbeddedPiAgent()` in `pi-embedded-runner/run.ts`: import { runEmbeddedPiAgent } from "./agents/pi-embedded-runner.js"; const result = await runEmbeddedPiAgent({ + agentId: "main", sessionId: "user-123", sessionKey: "main:whatsapp:+1234567890", - sessionFile: "sqlite-transcript://main/user-123.jsonl", workspaceDir: "/path/to/workspace", config: openclawConfig, prompt: "Hello, how are you?", @@ -305,7 +305,10 @@ applySystemPromptOverrideToSession(session, systemPromptOverride); Sessions are SQLite-backed event streams with tree structure (id/parentId linking). JSONL is legacy doctor-import/export/debug shape. OpenClaw owns the PI-compatible `SessionManager` shape behind `src/agents/transcript/session-transcript-contract.ts`: ```typescript -const sessionManager = openTranscriptSessionManager({ sessionFile: params.sessionFile }); +const sessionManager = openTranscriptSessionManager({ + agentId: params.agentId, + sessionId: params.sessionId, +}); ``` OpenClaw wraps this with `guardSessionManager()` for tool result safety. @@ -325,7 +328,7 @@ compaction: ```typescript const compactResult = await compactEmbeddedPiSessionDirect({ - sessionId, sessionFile, provider, model, ... + agentId, sessionId, provider, model, ... }); ``` diff --git a/docs/plugins/sdk-runtime.md b/docs/plugins/sdk-runtime.md index f339153fa6e..bc1285567d4 100644 --- a/docs/plugins/sdk-runtime.md +++ b/docs/plugins/sdk-runtime.md @@ -94,9 +94,9 @@ Provider and channel execution paths must use the active runtime config snapshot const agentDir = api.runtime.agent.resolveAgentDir(cfg); const sessionId = "my-plugin-task-1"; const result = await api.runtime.agent.runEmbeddedAgent({ + agentId, sessionId, runId: crypto.randomUUID(), - sessionFile: createSqliteSessionTranscriptLocator({ agentId, sessionId }), workspaceDir: api.runtime.agent.resolveAgentWorkspaceDir(cfg), prompt: "Summarize the latest changes", timeoutMs: api.runtime.agent.resolveAgentTimeoutMs(cfg), @@ -114,8 +114,6 @@ Provider and channel execution paths must use the active runtime config snapshot **SQLite session row helpers** are under `api.runtime.agent.session`: ```typescript - import { createSqliteSessionTranscriptLocator } from "openclaw/plugin-sdk/session-store-runtime"; - const entry = api.runtime.agent.session.getSessionEntry({ agentId, sessionKey }); await api.runtime.agent.session.patchSessionEntry({ agentId, @@ -125,7 +123,6 @@ Provider and channel execution paths must use the active runtime config snapshot thinkingLevel: "high", }), }); - const sessionFile = createSqliteSessionTranscriptLocator({ agentId, sessionId }); ``` Prefer row helpers such as `getSessionEntry(...)`, `listSessionEntries(...)`, `patchSessionEntry(...)`, and `upsertSessionEntry(...)` for runtime writes. They route through the SQLite session row store and preserve concurrent updates. Legacy `sessions.json` parsing belongs in doctor import code, not plugin runtime paths. diff --git a/scripts/e2e/lib/codex-npm-plugin-live/assertions.mjs b/scripts/e2e/lib/codex-npm-plugin-live/assertions.mjs index 3758dcf7e22..d91985869e8 100644 --- a/scripts/e2e/lib/codex-npm-plugin-live/assertions.mjs +++ b/scripts/e2e/lib/codex-npm-plugin-live/assertions.mjs @@ -362,17 +362,12 @@ function assertAgentTurn() { if (entry.modelOverride && entry.modelOverride !== modelRef) { throw new Error(`unexpected session model override: ${entry.modelOverride}`); } - if (typeof entry.sessionFile !== "string" || !entry.sessionFile.trim()) { - throw new Error( - `missing OpenClaw transcript key in SQLite session entry: ${entry.sessionFile}`, - ); - } const transcriptEvents = countAgentTranscriptEvents(sessionId); if (transcriptEvents <= 0) { throw new Error(`missing SQLite transcript events for ${sessionId}`); } - const binding = readOpenClawStateKvJson("codex_app_server_thread_bindings", entry.sessionFile); + const binding = readOpenClawStateKvJson("codex_app_server_thread_bindings", sessionId); if (binding.schemaVersion !== 1 || typeof binding.threadId !== "string") { throw new Error(`invalid Codex app-server binding: ${JSON.stringify(binding)}`); } diff --git a/scripts/e2e/lib/live-plugin-tool/assertions.mjs b/scripts/e2e/lib/live-plugin-tool/assertions.mjs index 93b99c41120..a112c3a40b9 100644 --- a/scripts/e2e/lib/live-plugin-tool/assertions.mjs +++ b/scripts/e2e/lib/live-plugin-tool/assertions.mjs @@ -1,5 +1,6 @@ import fs from "node:fs"; import path from "node:path"; +import { DatabaseSync } from "node:sqlite"; const command = process.argv[2]; const readJson = (file) => JSON.parse(fs.readFileSync(file, "utf8")); @@ -24,6 +25,32 @@ function configPath() { return process.env.OPENCLAW_CONFIG_PATH || path.join(stateDir(), "openclaw.json"); } +function agentDatabasePath(agentId = "main") { + return path.join(stateDir(), "agents", agentId, "agent", "openclaw-agent.sqlite"); +} + +function withSqliteDatabase(dbPath, callback) { + if (!fs.existsSync(dbPath)) { + throw new Error(`missing SQLite database: ${dbPath}`); + } + const db = new DatabaseSync(dbPath, { readOnly: true }); + try { + return callback(db); + } finally { + db.close(); + } +} + +function readMainAgentTranscriptText() { + return withSqliteDatabase(agentDatabasePath("main"), (db) => + db + .prepare("SELECT event_json FROM transcript_events ORDER BY session_id, seq") + .all() + .map((row) => String(row.event_json ?? "")) + .join("\n"), + ); +} + function realPathMaybe(filePath) { try { return fs.realpathSync(filePath); @@ -246,16 +273,9 @@ function assertAgentTurn() { `live agent reply did not contain tool slug ${expected}:\nstdout=${stdout}\nstderr=${stderr}`, ); } - const sessionsDir = path.join(stateDir(), "agents", "main", "sessions"); - const sessionFiles = fs - .readdirSync(sessionsDir, { recursive: true }) - .map((entry) => path.join(sessionsDir, String(entry))) - .filter((entry) => entry.endsWith(".jsonl") && fs.existsSync(entry)); - const transcript = sessionFiles.map((file) => fs.readFileSync(file, "utf8")).join("\n"); + const transcript = readMainAgentTranscriptText(); if (!transcript.includes(toolName) || !transcript.includes(expected)) { - throw new Error( - `session transcript did not show ${toolName} returning ${expected}; checked ${sessionFiles.join(", ")}`, - ); + throw new Error(`SQLite session transcript did not show ${toolName} returning ${expected}`); } } diff --git a/scripts/e2e/mcp-channels-seed.ts b/scripts/e2e/mcp-channels-seed.ts index 144fcf9806d..94699e9b045 100644 --- a/scripts/e2e/mcp-channels-seed.ts +++ b/scripts/e2e/mcp-channels-seed.ts @@ -1,7 +1,6 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; -import { createSqliteSessionTranscriptLocator } from "../../src/config/sessions/paths.ts"; import { upsertSessionEntry } from "../../src/config/sessions/store.ts"; import { replaceSqliteSessionTranscriptEvents } from "../../src/config/sessions/transcript-store.sqlite.ts"; import { resolveOpenClawAgentSqlitePath } from "../../src/state/openclaw-agent-db.ts"; @@ -11,10 +10,6 @@ async function main() { const stateDir = process.env.OPENCLAW_STATE_DIR?.trim() || path.join(os.homedir(), ".openclaw"); const configPath = process.env.OPENCLAW_CONFIG_PATH?.trim() || path.join(stateDir, "openclaw.json"); - const transcriptPath = createSqliteSessionTranscriptLocator({ - agentId: "main", - sessionId: "sess-main", - }); const now = Date.now(); await fs.mkdir(path.dirname(configPath), { recursive: true }); @@ -48,7 +43,6 @@ async function main() { sessionKey: "agent:main:main", entry: { sessionId: "sess-main", - sessionFile: transcriptPath, updatedAt: now, deliveryContext: { channel: "imessage", @@ -65,7 +59,6 @@ async function main() { replaceSqliteSessionTranscriptEvents({ agentId: "main", sessionId: "sess-main", - transcriptPath, now: () => now, events: [ { type: "session", version: 1, id: "sess-main" }, diff --git a/src/agents/main-session-restart-recovery.test.ts b/src/agents/main-session-restart-recovery.test.ts index bc2a06f6585..586d06eac3a 100644 --- a/src/agents/main-session-restart-recovery.test.ts +++ b/src/agents/main-session-restart-recovery.test.ts @@ -4,7 +4,6 @@ import path from "node:path"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import type { SessionEntry } from "../config/sessions.js"; import { listSessionEntries, upsertSessionEntry } from "../config/sessions/store.js"; -import { createSqliteSessionTranscriptLocator } from "../config/sessions/test-helpers/transcript-locator.js"; import { replaceSqliteSessionTranscriptEvents } from "../config/sessions/transcript-store.sqlite.js"; import { callGateway } from "../gateway/call.js"; import { closeOpenClawAgentDatabasesForTest } from "../state/openclaw-agent-db.js"; diff --git a/src/agents/pi-embedded-runner/run/attempt.spawn-workspace.test-support.ts b/src/agents/pi-embedded-runner/run/attempt.spawn-workspace.test-support.ts index 10970bde6e1..b265392e061 100644 --- a/src/agents/pi-embedded-runner/run/attempt.spawn-workspace.test-support.ts +++ b/src/agents/pi-embedded-runner/run/attempt.spawn-workspace.test-support.ts @@ -2,19 +2,18 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { expect, vi, type Mock } from "vitest"; -import { createSqliteSessionTranscriptLocator } from "../../../config/sessions/test-helpers/transcript-locator.js"; import type { AssembleResult, BootstrapResult, CompactResult, ContextEngineInfo, ContextEngineMaintenanceResult, + ContextEngineTranscriptScope, IngestBatchResult, IngestResult, } from "../../../context-engine/types.js"; import { formatErrorMessage } from "../../../infra/errors.js"; import type { PluginMetadataSnapshot } from "../../../plugins/plugin-metadata-snapshot.types.js"; -import { DEFAULT_AGENT_ID, resolveAgentIdFromSessionKey } from "../../../routing/session-key.js"; import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, @@ -1039,14 +1038,14 @@ export async function createContextEngineAttemptRunner(params: { bootstrap?: (params: { sessionId: string; sessionKey?: string; - transcriptLocator: string; + transcriptScope?: ContextEngineTranscriptScope; }) => Promise; maintain?: | boolean | ((params: { sessionId: string; sessionKey?: string; - transcriptLocator: string; + transcriptScope?: ContextEngineTranscriptScope; runtimeContext?: Record; }) => Promise<{ changed: boolean; @@ -1064,7 +1063,7 @@ export async function createContextEngineAttemptRunner(params: { afterTurn?: (params: { sessionId: string; sessionKey?: string; - transcriptLocator: string; + transcriptScope?: ContextEngineTranscriptScope; messages: AgentMessage[]; prePromptMessageCount: number; tokenBudget?: number; @@ -1083,7 +1082,7 @@ export async function createContextEngineAttemptRunner(params: { compact?: (params: { sessionId: string; sessionKey?: string; - transcriptLocator: string; + transcriptScope?: ContextEngineTranscriptScope; tokenBudget?: number; }) => Promise; info?: Partial; @@ -1100,10 +1099,6 @@ export async function createContextEngineAttemptRunner(params: { const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-ctx-engine-agent-")); const stateDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-ctx-engine-state-")); const sessionId = "embedded-session"; - const transcriptLocator = createSqliteSessionTranscriptLocator({ - agentId: resolveAgentIdFromSessionKey(params.sessionKey) ?? DEFAULT_AGENT_ID, - sessionId, - }); params.tempPaths.push(workspaceDir, agentDir, stateDir); const seedMessages: AgentMessage[] = params.sessionMessages ?? ([{ role: "user", content: "seed", timestamp: 1 }] as AgentMessage[]); diff --git a/src/config/sessions/test-helpers/transcript-locator.ts b/src/config/sessions/test-helpers/transcript-locator.ts deleted file mode 100644 index 7de115ee2e7..00000000000 --- a/src/config/sessions/test-helpers/transcript-locator.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { DEFAULT_AGENT_ID, normalizeAgentId } from "../../../routing/session-key.js"; -import { validateSessionId } from "../paths.js"; - -export const SQLITE_SESSION_TRANSCRIPT_LOCATOR_PREFIX = "sqlite-transcript://"; - -export function createSqliteSessionTranscriptLocator(params: { - agentId?: string; - sessionId: string; - topicId?: string | number; -}): string { - const agentId = normalizeAgentId(params.agentId ?? DEFAULT_AGENT_ID); - const sessionId = validateSessionId(params.sessionId); - const safeTopicId = - typeof params.topicId === "string" - ? encodeURIComponent(params.topicId) - : typeof params.topicId === "number" - ? String(params.topicId) - : undefined; - const topicSuffix = safeTopicId !== undefined ? `?topic=${safeTopicId}` : ""; - return `${SQLITE_SESSION_TRANSCRIPT_LOCATOR_PREFIX}${encodeURIComponent( - agentId, - )}/${encodeURIComponent(sessionId)}${topicSuffix}`; -} - -export function parseSqliteSessionTranscriptLocator(locator: string): - | { - agentId: string; - sessionId: string; - topicId?: string; - } - | undefined { - const trimmed = locator.trim(); - if (!trimmed.startsWith(SQLITE_SESSION_TRANSCRIPT_LOCATOR_PREFIX)) { - return undefined; - } - try { - const url = new URL(trimmed); - const agentId = decodeURIComponent(url.hostname).trim(); - const rawPath = decodeURIComponent(url.pathname.replace(/^\/+/u, "")).trim(); - if (!rawPath) { - return undefined; - } - const topicId = url.searchParams.get("topic") ?? undefined; - return { - agentId: normalizeAgentId(agentId), - sessionId: validateSessionId(rawPath), - ...(topicId ? { topicId } : {}), - }; - } catch { - return undefined; - } -} - -export function isSqliteSessionTranscriptLocator(locator: string | undefined): boolean { - return typeof locator === "string" && parseSqliteSessionTranscriptLocator(locator) !== undefined; -} - -export function resolveSessionTranscriptLocator( - sessionId: string, - opts?: { agentId?: string }, -): string { - return createSqliteSessionTranscriptLocator({ agentId: opts?.agentId, sessionId }); -} diff --git a/src/config/sessions/transcript-store.sqlite.test.ts b/src/config/sessions/transcript-store.sqlite.test.ts index 6c0596da746..b331b4b31eb 100644 --- a/src/config/sessions/transcript-store.sqlite.test.ts +++ b/src/config/sessions/transcript-store.sqlite.test.ts @@ -7,7 +7,6 @@ import { openOpenClawAgentDatabase, } from "../../state/openclaw-agent-db.js"; import { closeOpenClawStateDatabaseForTest } from "../../state/openclaw-state-db.js"; -import { createSqliteSessionTranscriptLocator } from "./test-helpers/transcript-locator.js"; import { appendSqliteSessionTranscriptEvent, appendSqliteSessionTranscriptMessage, @@ -31,7 +30,6 @@ afterEach(() => { describe("SQLite session transcript store", () => { it("appends transcript events with stable per-session sequence numbers", () => { const stateDir = createTempDir(); - const transcriptPath = path.join(stateDir, "session.jsonl"); expect( appendSqliteSessionTranscriptEvent({ diff --git a/src/config/sessions/transcript.test.ts b/src/config/sessions/transcript.test.ts index d45ef65a727..3b378297c06 100644 --- a/src/config/sessions/transcript.test.ts +++ b/src/config/sessions/transcript.test.ts @@ -7,7 +7,6 @@ import { closeOpenClawAgentDatabasesForTest } from "../../state/openclaw-agent-d import { closeOpenClawStateDatabaseForTest } from "../../state/openclaw-state-db.js"; import { upsertSessionEntry } from "./store.js"; import { useTempSessionsFixture } from "./test-helpers.js"; -import { createSqliteSessionTranscriptLocator } from "./test-helpers/transcript-locator.js"; import { appendSessionTranscriptMessage } from "./transcript-append.js"; import { appendSqliteSessionTranscriptEvent, diff --git a/src/cron/isolated-agent.session-identity.test.ts b/src/cron/isolated-agent.session-identity.test.ts index 3b4fc8986c4..2b677891a56 100644 --- a/src/cron/isolated-agent.session-identity.test.ts +++ b/src/cron/isolated-agent.session-identity.test.ts @@ -99,26 +99,30 @@ describe("runCronIsolatedAgentTurn session identity", () => { expect(res.status).toBe("ok"); const call = runEmbeddedPiAgentMock.mock.calls.at(-1)?.[0] as { + agentId?: string; + sessionId?: string; sessionKey?: string; workspaceDir?: string; - sessionFile?: string; }; + expect(call?.agentId).toBe("ops"); + expect(call?.sessionId).toBe(res.sessionId); expect(call?.sessionKey).toMatch(/^agent:ops:cron:job-ops:run:/); expect(call?.workspaceDir).toBe(opsWorkspace); - expect(call?.sessionFile).toMatch(/^sqlite-transcript:\/\/ops\/.+\.jsonl$/u); }); }); - it("passes sessionFile to isolated cron runs", async () => { + it("passes session identity to isolated cron runs", async () => { await withTempHome(async (home) => { - await runCronTurn(home, { + const { res } = await runCronTurn(home, { jobPayload: DEFAULT_AGENT_TURN_PAYLOAD, }); const call = runEmbeddedPiAgentMock.mock.calls.at(-1)?.[0] as { - sessionFile?: string; + agentId?: string; + sessionId?: string; }; - expect(call?.sessionFile).toMatch(/^sqlite-transcript:\/\/main\/.+\.jsonl$/u); + expect(call?.agentId).toBe("main"); + expect(call?.sessionId).toBe(res.sessionId); }); });