mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 05:16:23 -06:00
refactor: route SDK session compatibility through seam (#89203)
This commit is contained in:
@@ -1,4 +1,7 @@
|
||||
// Plugin runtime index tests cover runtime entrypoint exports and registry setup.
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "../../agents/defaults.js";
|
||||
import {
|
||||
@@ -6,13 +9,13 @@ import {
|
||||
setRuntimeConfigSnapshot,
|
||||
type OpenClawConfig,
|
||||
} from "../../config/config.js";
|
||||
import { listSessionEntries, loadSessionEntry } from "../../config/sessions/session-accessor.js";
|
||||
import { onAgentEvent } from "../../infra/agent-events.js";
|
||||
import {
|
||||
requestHeartbeat,
|
||||
resetHeartbeatWakeStateForTests,
|
||||
setHeartbeatWakeHandler,
|
||||
} from "../../infra/heartbeat-wake.js";
|
||||
import * as jsonFiles from "../../infra/json-files.js";
|
||||
import * as execModule from "../../process/exec.js";
|
||||
import { onSessionTranscriptUpdate } from "../../sessions/transcript-events.js";
|
||||
import { VERSION } from "../../version.js";
|
||||
@@ -314,16 +317,16 @@ describe("plugin runtime command execution", () => {
|
||||
]);
|
||||
expect(runtime.agent.runEmbeddedPiAgent).toBe(runtime.agent.runEmbeddedAgent);
|
||||
expectFunctionKeys(runtime.agent.session as Record<string, unknown>, [
|
||||
"loadSessionStore",
|
||||
"getSessionEntry",
|
||||
"listSessionEntries",
|
||||
"patchSessionEntry",
|
||||
"upsertSessionEntry",
|
||||
"saveSessionStore",
|
||||
"updateSessionStore",
|
||||
"updateSessionStoreEntry",
|
||||
"resolveSessionFilePath",
|
||||
]);
|
||||
expect(runtime.agent.session.getSessionEntry).toBe(loadSessionEntry);
|
||||
expect(runtime.agent.session.listSessionEntries).toBe(listSessionEntries);
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -340,6 +343,41 @@ describe("plugin runtime command execution", () => {
|
||||
expectRuntimeShape(assert);
|
||||
});
|
||||
|
||||
it("preserves requireWriteSuccess through runtime session entry updates", async () => {
|
||||
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-runtime-session-store-"));
|
||||
const storePath = path.join(tempDir, "sessions.json");
|
||||
const sessionKey = "agent:main:main";
|
||||
const runtime = createPluginRuntime();
|
||||
|
||||
try {
|
||||
await runtime.agent.session.upsertSessionEntry({
|
||||
sessionKey,
|
||||
storePath,
|
||||
entry: {
|
||||
sessionId: "session-1",
|
||||
updatedAt: 10,
|
||||
},
|
||||
});
|
||||
const writeError = Object.assign(new Error("write failed"), { code: "ENOENT" });
|
||||
const writeSpy = vi.spyOn(jsonFiles, "writeTextAtomic").mockRejectedValue(writeError);
|
||||
|
||||
try {
|
||||
await expect(
|
||||
runtime.agent.session.updateSessionStoreEntry({
|
||||
sessionKey,
|
||||
storePath,
|
||||
requireWriteSuccess: true,
|
||||
update: () => ({ model: "gpt-5.5" }),
|
||||
}),
|
||||
).rejects.toBe(writeError);
|
||||
} finally {
|
||||
writeSpy.mockRestore();
|
||||
}
|
||||
} finally {
|
||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("modelAuth wrappers strip agentDir and store to prevent credential steering", async () => {
|
||||
// The wrappers should not forward agentDir or store from plugin callers.
|
||||
// We verify this by checking the wrapper functions exist and are not the
|
||||
|
||||
@@ -12,21 +12,65 @@ import { normalizeThinkLevel, resolveThinkingProfile } from "../../auto-reply/th
|
||||
import { getRuntimeConfig } from "../../config/config.js";
|
||||
import { resolveSessionFilePath, resolveStorePath } from "../../config/sessions/paths.js";
|
||||
import {
|
||||
listSessionEntries,
|
||||
loadSessionEntry as getSessionEntry,
|
||||
listSessionEntries as listAccessorSessionEntries,
|
||||
loadSessionEntry,
|
||||
patchSessionEntry as patchAccessorSessionEntry,
|
||||
replaceSessionEntry,
|
||||
type SessionAccessScope,
|
||||
updateSessionEntry,
|
||||
} from "../../config/sessions/session-accessor.js";
|
||||
import {
|
||||
loadSessionStore,
|
||||
patchSessionEntry,
|
||||
saveSessionStore,
|
||||
updateSessionStore,
|
||||
updateSessionStoreEntry,
|
||||
upsertSessionEntry,
|
||||
type ResolvedSessionMaintenanceConfig,
|
||||
} from "../../config/sessions/store.js";
|
||||
import type { SessionEntry } from "../../config/sessions/types.js";
|
||||
import { createLazyRuntimeMethod, createLazyRuntimeModule } from "../../shared/lazy-runtime.js";
|
||||
import { defineCachedValue } from "./runtime-cache.js";
|
||||
import type { PluginRuntime } from "./types.js";
|
||||
|
||||
type RuntimeSessionStoreReadParams = {
|
||||
agentId?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
hydrateSkillPromptRefs?: boolean;
|
||||
sessionKey: string;
|
||||
storePath?: string;
|
||||
};
|
||||
|
||||
type RuntimeSessionStoreListParams = Partial<Omit<RuntimeSessionStoreReadParams, "sessionKey">>;
|
||||
|
||||
type RuntimeSessionStoreEntrySummary = {
|
||||
sessionKey: string;
|
||||
entry: SessionEntry;
|
||||
};
|
||||
|
||||
type RuntimeSessionStoreEntryUpdateParams = {
|
||||
storePath: string;
|
||||
sessionKey: string;
|
||||
update: (
|
||||
entry: SessionEntry,
|
||||
) => Promise<Partial<SessionEntry> | null> | Partial<SessionEntry> | null;
|
||||
skipMaintenance?: boolean;
|
||||
takeCacheOwnership?: boolean;
|
||||
requireWriteSuccess?: boolean;
|
||||
};
|
||||
|
||||
type RuntimeSessionStoreEntryPatchParams = RuntimeSessionStoreReadParams & {
|
||||
fallbackEntry?: SessionEntry;
|
||||
maintenanceConfig?: ResolvedSessionMaintenanceConfig;
|
||||
preserveActivity?: boolean;
|
||||
replaceEntry?: boolean;
|
||||
update: (
|
||||
entry: SessionEntry,
|
||||
context: { existingEntry?: SessionEntry },
|
||||
) => Promise<Partial<SessionEntry> | null> | Partial<SessionEntry> | null;
|
||||
};
|
||||
|
||||
type RuntimeUpsertSessionEntryParams = RuntimeSessionStoreReadParams & {
|
||||
entry: SessionEntry;
|
||||
};
|
||||
|
||||
const loadEmbeddedAgentRuntime = createLazyRuntimeModule(
|
||||
() => import("./runtime-embedded-agent.runtime.js"),
|
||||
);
|
||||
@@ -41,6 +85,73 @@ function resolveRuntimeThinkingCatalog(
|
||||
return configuredCatalog.length > 0 ? configuredCatalog : undefined;
|
||||
}
|
||||
|
||||
function toSessionAccessScope(params: RuntimeSessionStoreReadParams): SessionAccessScope {
|
||||
// Keep plugin runtime parameters aligned with the public SDK wrapper while
|
||||
// avoiding direct exposure of internal accessor-only options.
|
||||
return {
|
||||
sessionKey: params.sessionKey,
|
||||
...(params.agentId !== undefined ? { agentId: params.agentId } : {}),
|
||||
...(params.env !== undefined ? { env: params.env } : {}),
|
||||
...(params.hydrateSkillPromptRefs !== undefined
|
||||
? { hydrateSkillPromptRefs: params.hydrateSkillPromptRefs }
|
||||
: {}),
|
||||
...(params.storePath !== undefined ? { storePath: params.storePath } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
function getSessionEntry(params: RuntimeSessionStoreReadParams): SessionEntry | undefined {
|
||||
return loadSessionEntry(toSessionAccessScope(params));
|
||||
}
|
||||
|
||||
function listSessionEntries(
|
||||
params: RuntimeSessionStoreListParams = {},
|
||||
): RuntimeSessionStoreEntrySummary[] {
|
||||
return listAccessorSessionEntries({
|
||||
...(params.agentId !== undefined ? { agentId: params.agentId } : {}),
|
||||
...(params.env !== undefined ? { env: params.env } : {}),
|
||||
...(params.hydrateSkillPromptRefs !== undefined
|
||||
? { hydrateSkillPromptRefs: params.hydrateSkillPromptRefs }
|
||||
: {}),
|
||||
...(params.storePath !== undefined ? { storePath: params.storePath } : {}),
|
||||
});
|
||||
}
|
||||
|
||||
async function patchSessionEntry(
|
||||
params: RuntimeSessionStoreEntryPatchParams,
|
||||
): Promise<SessionEntry | null> {
|
||||
return await patchAccessorSessionEntry(toSessionAccessScope(params), params.update, {
|
||||
fallbackEntry: params.fallbackEntry,
|
||||
maintenanceConfig: params.maintenanceConfig,
|
||||
preserveActivity: params.preserveActivity,
|
||||
replaceEntry: params.replaceEntry,
|
||||
});
|
||||
}
|
||||
|
||||
async function updateSessionStoreEntry(
|
||||
params: RuntimeSessionStoreEntryUpdateParams,
|
||||
): Promise<SessionEntry | null> {
|
||||
// Maintainer note: keep the legacy object-parameter API here, but route
|
||||
// mutations through the session accessor boundary.
|
||||
return await updateSessionEntry(
|
||||
{
|
||||
sessionKey: params.sessionKey,
|
||||
storePath: params.storePath,
|
||||
},
|
||||
params.update,
|
||||
{
|
||||
skipMaintenance: params.skipMaintenance,
|
||||
takeCacheOwnership: params.takeCacheOwnership,
|
||||
requireWriteSuccess: params.requireWriteSuccess,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async function upsertSessionEntry(params: RuntimeUpsertSessionEntryParams): Promise<void> {
|
||||
// Maintainer note: this compatibility helper has full-entry replacement
|
||||
// semantics, so removed fields must not survive as merge leftovers.
|
||||
await replaceSessionEntry(toSessionAccessScope(params), params.entry);
|
||||
}
|
||||
|
||||
/** Creates the plugin runtime agent facade with lazy embedded-agent/session helpers. */
|
||||
export function createRuntimeAgent(): PluginRuntime["agent"] {
|
||||
const agentRuntime = {
|
||||
|
||||
@@ -59,6 +59,42 @@ type RuntimeReplaceConfigFileParams = {
|
||||
afterWrite: RuntimeConfigAfterWrite;
|
||||
writeOptions?: RuntimeWriteConfigOptions;
|
||||
};
|
||||
type RuntimeSessionEntry = import("../../config/sessions/types.js").SessionEntry;
|
||||
type RuntimeSessionStoreReadParams = {
|
||||
agentId?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
hydrateSkillPromptRefs?: boolean;
|
||||
sessionKey: string;
|
||||
storePath?: string;
|
||||
};
|
||||
type RuntimeSessionStoreListParams = Partial<Omit<RuntimeSessionStoreReadParams, "sessionKey">>;
|
||||
type RuntimeSessionStoreEntrySummary = {
|
||||
sessionKey: string;
|
||||
entry: RuntimeSessionEntry;
|
||||
};
|
||||
type RuntimeSessionStoreEntryPatchParams = RuntimeSessionStoreReadParams & {
|
||||
fallbackEntry?: RuntimeSessionEntry;
|
||||
maintenanceConfig?: import("../../config/sessions/store.js").ResolvedSessionMaintenanceConfig;
|
||||
preserveActivity?: boolean;
|
||||
replaceEntry?: boolean;
|
||||
update: (
|
||||
entry: RuntimeSessionEntry,
|
||||
context: { existingEntry?: RuntimeSessionEntry },
|
||||
) => Promise<Partial<RuntimeSessionEntry> | null> | Partial<RuntimeSessionEntry> | null;
|
||||
};
|
||||
type RuntimeUpsertSessionEntryParams = RuntimeSessionStoreReadParams & {
|
||||
entry: RuntimeSessionEntry;
|
||||
};
|
||||
type RuntimeSessionStoreEntryUpdateParams = {
|
||||
storePath: string;
|
||||
sessionKey: string;
|
||||
update: (
|
||||
entry: RuntimeSessionEntry,
|
||||
) => Promise<Partial<RuntimeSessionEntry> | null> | Partial<RuntimeSessionEntry> | null;
|
||||
skipMaintenance?: boolean;
|
||||
takeCacheOwnership?: boolean;
|
||||
requireWriteSuccess?: boolean;
|
||||
};
|
||||
export type PluginRuntimeThinkingPolicyRequest = {
|
||||
provider?: string | null;
|
||||
model?: string | null;
|
||||
@@ -205,19 +241,44 @@ export type PluginRuntimeCore = {
|
||||
ensureAgentWorkspace: typeof import("../../agents/workspace.js").ensureAgentWorkspace;
|
||||
session: {
|
||||
resolveStorePath: typeof import("../../config/sessions/paths.js").resolveStorePath;
|
||||
getSessionEntry: typeof import("../../config/sessions/session-accessor.js").loadSessionEntry;
|
||||
listSessionEntries: typeof import("../../config/sessions/session-accessor.js").listSessionEntries;
|
||||
patchSessionEntry: typeof import("../../config/sessions/store.js").patchSessionEntry;
|
||||
upsertSessionEntry: typeof import("../../config/sessions/store.js").upsertSessionEntry;
|
||||
getSessionEntry: (params: RuntimeSessionStoreReadParams) => RuntimeSessionEntry | undefined;
|
||||
listSessionEntries: (
|
||||
params?: RuntimeSessionStoreListParams,
|
||||
) => RuntimeSessionStoreEntrySummary[];
|
||||
patchSessionEntry: (
|
||||
params: RuntimeSessionStoreEntryPatchParams,
|
||||
) => Promise<RuntimeSessionEntry | null>;
|
||||
upsertSessionEntry: (params: RuntimeUpsertSessionEntryParams) => Promise<void>;
|
||||
/**
|
||||
* @deprecated Use getSessionEntry/listSessionEntries for reads and
|
||||
* patchSessionEntry/upsertSessionEntry for writes. This keeps the legacy
|
||||
* mutable whole-store compatibility shape.
|
||||
* patchSessionEntry/upsertSessionEntry for writes. This whole-store
|
||||
* helper is kept only during the transition before SQLite migration.
|
||||
* Callers must migrate away from reading sessions.json directly.
|
||||
*/
|
||||
loadSessionStore: typeof import("../../config/sessions/store-load.js").loadSessionStore;
|
||||
/**
|
||||
* @deprecated Use patchSessionEntry/upsertSessionEntry for writes. This
|
||||
* whole-store helper is kept only during the transition before SQLite
|
||||
* migration. Callers must migrate away from writing sessions.json
|
||||
* directly.
|
||||
*/
|
||||
saveSessionStore: import("../../config/sessions/runtime-types.js").SaveSessionStore;
|
||||
/**
|
||||
* @deprecated Use patchSessionEntry/upsertSessionEntry for writes. This
|
||||
* whole-store helper is kept only during the transition before SQLite
|
||||
* migration. Callers must migrate away from updating sessions.json
|
||||
* directly.
|
||||
*/
|
||||
updateSessionStore: typeof import("../../config/sessions/store.js").updateSessionStore;
|
||||
updateSessionStoreEntry: typeof import("../../config/sessions/store.js").updateSessionStoreEntry;
|
||||
updateSessionStoreEntry: (
|
||||
params: RuntimeSessionStoreEntryUpdateParams,
|
||||
) => Promise<RuntimeSessionEntry | null>;
|
||||
/**
|
||||
* @deprecated Use getSessionEntry to read session metadata by
|
||||
* agent/session identity. This file-path helper is kept only during the
|
||||
* transition before SQLite migration. Callers must migrate away from
|
||||
* resolving transcript file paths directly.
|
||||
*/
|
||||
resolveSessionFilePath: typeof import("../../config/sessions/paths.js").resolveSessionFilePath;
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user