refactor: dedupe shared config type definitions

This commit is contained in:
Peter Steinberger
2026-02-22 18:36:42 +00:00
parent 12635de1c7
commit 4a88c579ba
7 changed files with 127 additions and 220 deletions

View File

@@ -9,6 +9,22 @@ import {
saveSessionStore,
} from "./sessions.js";
function createSessionEntry(overrides: Partial<SessionEntry> = {}): SessionEntry {
return {
sessionId: "id-1",
updatedAt: Date.now(),
displayName: "Test Session 1",
...overrides,
};
}
function createSingleSessionStore(
entry: SessionEntry = createSessionEntry(),
key = "session:1",
): Record<string, SessionEntry> {
return { [key]: entry };
}
describe("Session Store Cache", () => {
let fixtureRoot = "";
let caseId = 0;
@@ -43,13 +59,7 @@ describe("Session Store Cache", () => {
});
it("should load session store from disk on first call", async () => {
const testStore: Record<string, SessionEntry> = {
"session:1": {
sessionId: "id-1",
updatedAt: Date.now(),
displayName: "Test Session 1",
},
};
const testStore = createSingleSessionStore();
// Write test data
await saveSessionStore(storePath, testStore);
@@ -60,13 +70,7 @@ describe("Session Store Cache", () => {
});
it("should cache session store on first load when file is unchanged", async () => {
const testStore: Record<string, SessionEntry> = {
"session:1": {
sessionId: "id-1",
updatedAt: Date.now(),
displayName: "Test Session 1",
},
};
const testStore = createSingleSessionStore();
await saveSessionStore(storePath, testStore);
@@ -84,17 +88,15 @@ describe("Session Store Cache", () => {
});
it("should not allow cached session mutations to leak across loads", async () => {
const testStore: Record<string, SessionEntry> = {
"session:1": {
sessionId: "id-1",
updatedAt: Date.now(),
const testStore = createSingleSessionStore(
createSessionEntry({
cliSessionIds: { openai: "sess-1" },
skillsSnapshot: {
prompt: "skills",
skills: [{ name: "alpha" }],
},
},
};
}),
);
await saveSessionStore(storePath, testStore);
@@ -110,13 +112,7 @@ describe("Session Store Cache", () => {
});
it("should refresh cache when store file changes on disk", async () => {
const testStore: Record<string, SessionEntry> = {
"session:1": {
sessionId: "id-1",
updatedAt: Date.now(),
displayName: "Test Session 1",
},
};
const testStore = createSingleSessionStore();
await saveSessionStore(storePath, testStore);
@@ -138,13 +134,7 @@ describe("Session Store Cache", () => {
});
it("should invalidate cache on write", async () => {
const testStore: Record<string, SessionEntry> = {
"session:1": {
sessionId: "id-1",
updatedAt: Date.now(),
displayName: "Test Session 1",
},
};
const testStore = createSingleSessionStore();
await saveSessionStore(storePath, testStore);
@@ -172,13 +162,7 @@ describe("Session Store Cache", () => {
process.env.OPENCLAW_SESSION_CACHE_TTL_MS = "0";
clearSessionStoreCacheForTest();
const testStore: Record<string, SessionEntry> = {
"session:1": {
sessionId: "id-1",
updatedAt: Date.now(),
displayName: "Test Session 1",
},
};
const testStore = createSingleSessionStore();
await saveSessionStore(storePath, testStore);
@@ -187,13 +171,10 @@ describe("Session Store Cache", () => {
expect(loaded1).toEqual(testStore);
// Modify file on disk
const modifiedStore: Record<string, SessionEntry> = {
"session:2": {
sessionId: "id-2",
updatedAt: Date.now(),
displayName: "Test Session 2",
},
};
const modifiedStore = createSingleSessionStore(
createSessionEntry({ sessionId: "id-2", displayName: "Test Session 2" }),
"session:2",
);
fs.writeFileSync(storePath, JSON.stringify(modifiedStore, null, 2));
// Second load - should read from disk (cache disabled)

View File

@@ -1,15 +1,11 @@
import type { ChannelId } from "../channels/plugins/types.js";
import type { AgentModelConfig, AgentSandboxConfig } from "./types.agents-shared.js";
import type {
BlockStreamingChunkConfig,
BlockStreamingCoalesceConfig,
HumanDelayConfig,
TypingMode,
} from "./types.base.js";
import type {
SandboxBrowserSettings,
SandboxDockerSettings,
SandboxPruneSettings,
} from "./types.sandbox.js";
import type { MemorySearchConfig } from "./types.tools.js";
export type AgentModelEntryConfig = {
@@ -248,40 +244,12 @@ export type AgentDefaultsConfig = {
/** Auto-archive sub-agent sessions after N minutes (default: 60). */
archiveAfterMinutes?: number;
/** Default model selection for spawned sub-agents (string or {primary,fallbacks}). */
model?: string | { primary?: string; fallbacks?: string[] };
model?: AgentModelConfig;
/** Default thinking level for spawned sub-agents (e.g. "off", "low", "medium", "high"). */
thinking?: string;
};
/** Optional sandbox settings for non-main sessions. */
sandbox?: {
/** Enable sandboxing for sessions. */
mode?: "off" | "non-main" | "all";
/**
* Agent workspace access inside the sandbox.
* - "none": do not mount the agent workspace into the container; use a sandbox workspace under workspaceRoot
* - "ro": mount the agent workspace read-only; disables write/edit tools
* - "rw": mount the agent workspace read/write; enables write/edit tools
*/
workspaceAccess?: "none" | "ro" | "rw";
/**
* Session tools visibility for sandboxed sessions.
* - "spawned": only allow session tools to target the current session and sessions spawned from it (default)
* - "all": allow session tools to target any session
*/
sessionToolsVisibility?: "spawned" | "all";
/** Container/workspace scope for sandbox isolation. */
scope?: "session" | "agent" | "shared";
/** Legacy alias for scope ("session" when true, "shared" when false). */
perSession?: boolean;
/** Root directory for sandbox workspaces. */
workspaceRoot?: string;
/** Docker-specific sandbox settings. */
docker?: SandboxDockerSettings;
/** Optional sandboxed browser settings. */
browser?: SandboxBrowserSettings;
/** Auto-prune sandbox containers. */
prune?: SandboxPruneSettings;
};
sandbox?: AgentSandboxConfig;
};
export type AgentCompactionMode = "default" | "safeguard";

View File

@@ -0,0 +1,37 @@
import type {
SandboxBrowserSettings,
SandboxDockerSettings,
SandboxPruneSettings,
} from "./types.sandbox.js";
export type AgentModelConfig =
| string
| {
/** Primary model (provider/model). */
primary?: string;
/** Per-agent model fallbacks (provider/model). */
fallbacks?: string[];
};
export type AgentSandboxConfig = {
mode?: "off" | "non-main" | "all";
/** Agent workspace access inside the sandbox. */
workspaceAccess?: "none" | "ro" | "rw";
/**
* Session tools visibility for sandboxed sessions.
* - "spawned": only allow session tools to target sessions spawned from this session (default)
* - "all": allow session tools to target any session
*/
sessionToolsVisibility?: "spawned" | "all";
/** Container/workspace scope for sandbox isolation. */
scope?: "session" | "agent" | "shared";
/** Legacy alias for scope ("session" when true, "shared" when false). */
perSession?: boolean;
workspaceRoot?: string;
/** Docker-specific sandbox settings. */
docker?: SandboxDockerSettings;
/** Optional sandboxed browser settings. */
browser?: SandboxBrowserSettings;
/** Auto-prune sandbox settings. */
prune?: SandboxPruneSettings;
};

View File

@@ -1,23 +1,10 @@
import type { ChatType } from "../channels/chat-type.js";
import type { AgentDefaultsConfig } from "./types.agent-defaults.js";
import type { AgentModelConfig, AgentSandboxConfig } from "./types.agents-shared.js";
import type { HumanDelayConfig, IdentityConfig } from "./types.base.js";
import type { GroupChatConfig } from "./types.messages.js";
import type {
SandboxBrowserSettings,
SandboxDockerSettings,
SandboxPruneSettings,
} from "./types.sandbox.js";
import type { AgentToolsConfig, MemorySearchConfig } from "./types.tools.js";
export type AgentModelConfig =
| string
| {
/** Primary model (provider/model). */
primary?: string;
/** Per-agent model fallbacks (provider/model). */
fallbacks?: string[];
};
export type AgentConfig = {
id: string;
default?: boolean;
@@ -38,30 +25,10 @@ export type AgentConfig = {
/** Allow spawning sub-agents under other agent ids. Use "*" to allow any. */
allowAgents?: string[];
/** Per-agent default model for spawned sub-agents (string or {primary,fallbacks}). */
model?: string | { primary?: string; fallbacks?: string[] };
};
sandbox?: {
mode?: "off" | "non-main" | "all";
/** Agent workspace access inside the sandbox. */
workspaceAccess?: "none" | "ro" | "rw";
/**
* Session tools visibility for sandboxed sessions.
* - "spawned": only allow session tools to target sessions spawned from this session (default)
* - "all": allow session tools to target any session
*/
sessionToolsVisibility?: "spawned" | "all";
/** Container/workspace scope for sandbox isolation. */
scope?: "session" | "agent" | "shared";
/** Legacy alias for scope ("session" when true, "shared" when false). */
perSession?: boolean;
workspaceRoot?: string;
/** Docker-specific sandbox overrides for this agent. */
docker?: SandboxDockerSettings;
/** Optional sandboxed browser overrides for this agent. */
browser?: SandboxBrowserSettings;
/** Auto-prune overrides for this agent. */
prune?: SandboxPruneSettings;
model?: AgentModelConfig;
};
/** Optional per-agent sandbox overrides. */
sandbox?: AgentSandboxConfig;
tools?: AgentToolsConfig;
};

View File

@@ -0,0 +1,50 @@
import type {
BlockStreamingCoalesceConfig,
DmPolicy,
GroupPolicy,
MarkdownConfig,
} from "./types.base.js";
import type { ChannelHeartbeatVisibilityConfig } from "./types.channels.js";
import type { DmConfig } from "./types.messages.js";
export type CommonChannelMessagingConfig = {
/** Optional display name for this account (used in CLI/UI lists). */
name?: string;
/** Optional provider capability tags used for agent/runtime guidance. */
capabilities?: string[];
/** Markdown formatting overrides (tables). */
markdown?: MarkdownConfig;
/** Allow channel-initiated config writes (default: true). */
configWrites?: boolean;
/** If false, do not start this account. Default: true. */
enabled?: boolean;
/** Direct message access policy (default: pairing). */
dmPolicy?: DmPolicy;
/** Optional allowlist for inbound DM senders. */
allowFrom?: Array<string | number>;
/** Default delivery target for CLI --deliver when no explicit --reply-to is provided. */
defaultTo?: string;
/** Optional allowlist for group/channel senders. */
groupAllowFrom?: Array<string | number>;
/** Group/channel message handling policy. */
groupPolicy?: GroupPolicy;
/** Max group/channel messages to keep as history context (0 disables). */
historyLimit?: number;
/** Max DM turns to keep as history context. */
dmHistoryLimit?: number;
/** Per-DM config overrides keyed by sender ID. */
dms?: Record<string, DmConfig>;
/** Outbound text chunk size (chars). */
textChunkLimit?: number;
/** Chunking mode: "length" (default) splits by size; "newline" splits on every newline. */
chunkMode?: "length" | "newline";
blockStreaming?: boolean;
/** Merge streamed block replies before sending. */
blockStreamingCoalesce?: BlockStreamingCoalesceConfig;
/** Heartbeat visibility settings for this channel. */
heartbeat?: ChannelHeartbeatVisibilityConfig;
/** Outbound response prefix override for this channel/account. */
responsePrefix?: string;
/** Max outbound media size in MB. */
mediaMaxMb?: number;
};

View File

@@ -1,24 +1,7 @@
import type {
BlockStreamingCoalesceConfig,
DmPolicy,
GroupPolicy,
MarkdownConfig,
} from "./types.base.js";
import type { ChannelHeartbeatVisibilityConfig } from "./types.channels.js";
import type { DmConfig } from "./types.messages.js";
import type { CommonChannelMessagingConfig } from "./types.channel-messaging-common.js";
import type { GroupToolPolicyBySenderConfig, GroupToolPolicyConfig } from "./types.tools.js";
export type IrcAccountConfig = {
/** Optional display name for this account (used in CLI/UI lists). */
name?: string;
/** Optional provider capability tags used for agent/runtime guidance. */
capabilities?: string[];
/** Markdown formatting overrides (tables). */
markdown?: MarkdownConfig;
/** Allow channel-initiated config writes (default: true). */
configWrites?: boolean;
/** If false, do not start this IRC account. Default: true. */
enabled?: boolean;
export type IrcAccountConfig = CommonChannelMessagingConfig & {
/** IRC server hostname (example: irc.libera.chat). */
host?: string;
/** IRC server port (default: 6697 with TLS, otherwise 6667). */
@@ -52,34 +35,8 @@ export type IrcAccountConfig = {
};
/** Auto-join channel list at connect (example: ["#openclaw"]). */
channels?: string[];
/** Direct message access policy (default: pairing). */
dmPolicy?: DmPolicy;
/** Optional allowlist for inbound DM senders. */
allowFrom?: Array<string | number>;
/** Default delivery target for CLI --deliver when no explicit --reply-to is provided. */
defaultTo?: string;
/** Optional allowlist for IRC channel senders. */
groupAllowFrom?: Array<string | number>;
/**
* Controls how channel messages are handled:
* - "open": channels bypass allowFrom; mention-gating applies
* - "disabled": block all channel messages entirely
* - "allowlist": only allow channel messages from senders in groupAllowFrom/allowFrom
*/
groupPolicy?: GroupPolicy;
/** Max channel messages to keep as history context (0 disables). */
historyLimit?: number;
/** Max DM turns to keep as history context. */
dmHistoryLimit?: number;
/** Per-DM config overrides keyed by sender ID. */
dms?: Record<string, DmConfig>;
/** Outbound text chunk size (chars). Default: 350. */
textChunkLimit?: number;
/** Chunking mode: "length" (default) splits by size; "newline" splits on every newline. */
chunkMode?: "length" | "newline";
blockStreaming?: boolean;
/** Merge streamed block replies before sending. */
blockStreamingCoalesce?: BlockStreamingCoalesceConfig;
groups?: Record<
string,
{
@@ -94,12 +51,6 @@ export type IrcAccountConfig = {
>;
/** Optional mention patterns specific to IRC channel messages. */
mentionPatterns?: string[];
/** Heartbeat visibility settings for this channel. */
heartbeat?: ChannelHeartbeatVisibilityConfig;
/** Outbound response prefix override for this channel/account. */
responsePrefix?: string;
/** Max outbound media size in MB. */
mediaMaxMb?: number;
};
export type IrcConfig = {

View File

@@ -1,26 +1,9 @@
import type {
BlockStreamingCoalesceConfig,
DmPolicy,
GroupPolicy,
MarkdownConfig,
} from "./types.base.js";
import type { ChannelHeartbeatVisibilityConfig } from "./types.channels.js";
import type { DmConfig } from "./types.messages.js";
import type { CommonChannelMessagingConfig } from "./types.channel-messaging-common.js";
export type SignalReactionNotificationMode = "off" | "own" | "all" | "allowlist";
export type SignalReactionLevel = "off" | "ack" | "minimal" | "extensive";
export type SignalAccountConfig = {
/** Optional display name for this account (used in CLI/UI lists). */
name?: string;
/** Optional provider capability tags used for agent/runtime guidance. */
capabilities?: string[];
/** Markdown formatting overrides (tables). */
markdown?: MarkdownConfig;
/** Allow channel-initiated config writes (default: true). */
configWrites?: boolean;
/** If false, do not start this Signal account. Default: true. */
enabled?: boolean;
export type SignalAccountConfig = CommonChannelMessagingConfig & {
/** Optional explicit E.164 account for signal-cli. */
account?: string;
/** Optional full base URL for signal-cli HTTP daemon. */
@@ -39,34 +22,8 @@ export type SignalAccountConfig = {
ignoreAttachments?: boolean;
ignoreStories?: boolean;
sendReadReceipts?: boolean;
/** Direct message access policy (default: pairing). */
dmPolicy?: DmPolicy;
allowFrom?: Array<string | number>;
/** Default delivery target for CLI --deliver when no explicit --reply-to is provided. */
defaultTo?: string;
/** Optional allowlist for Signal group senders (E.164). */
groupAllowFrom?: Array<string | number>;
/**
* Controls how group messages are handled:
* - "open": groups bypass allowFrom, no extra gating
* - "disabled": block all group messages
* - "allowlist": only allow group messages from senders in groupAllowFrom/allowFrom
*/
groupPolicy?: GroupPolicy;
/** Max group messages to keep as history context (0 disables). */
historyLimit?: number;
/** Max DM turns to keep as history context. */
dmHistoryLimit?: number;
/** Per-DM config overrides keyed by user ID. */
dms?: Record<string, DmConfig>;
/** Outbound text chunk size (chars). Default: 4000. */
textChunkLimit?: number;
/** Chunking mode: "length" (default) splits by size; "newline" splits on every newline. */
chunkMode?: "length" | "newline";
blockStreaming?: boolean;
/** Merge streamed block replies before sending. */
blockStreamingCoalesce?: BlockStreamingCoalesceConfig;
mediaMaxMb?: number;
/** Reaction notification mode (off|own|all|allowlist). Default: own. */
reactionNotifications?: SignalReactionNotificationMode;
/** Allowlist for reaction notifications when mode is allowlist. */
@@ -84,10 +41,6 @@ export type SignalAccountConfig = {
* - "extensive": Agent can react liberally
*/
reactionLevel?: SignalReactionLevel;
/** Heartbeat visibility settings for this channel. */
heartbeat?: ChannelHeartbeatVisibilityConfig;
/** Outbound response prefix override for this channel/account. */
responsePrefix?: string;
};
export type SignalConfig = {