Vendor OpenClaw source as Adolf fork baseline
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled

Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11),
free to diverge. Tree copied sans upstream .git; upstream remote added for
future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19.
Preserves docs/ARCHITECTURE.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
This commit is contained in:
2026-07-05 09:36:54 +00:00
parent 3216769225
commit bedb527145
21108 changed files with 6010766 additions and 0 deletions

View File

@@ -0,0 +1,140 @@
// Active Memory tests cover config plugin behavior.
import fs from "node:fs";
import {
type JsonSchemaObject,
validateJsonSchemaValue,
} from "openclaw/plugin-sdk/json-schema-runtime";
import { describe, expect, it } from "vitest";
const manifest = JSON.parse(
fs.readFileSync(new URL("./openclaw.plugin.json", import.meta.url), "utf-8"),
) as { configSchema: JsonSchemaObject };
describe("active-memory manifest config schema", () => {
it("accepts modelFallback for CLI and config.patch flows", () => {
const result = validateJsonSchemaValue({
schema: manifest.configSchema,
cacheKey: "active-memory.manifest.model-fallback",
value: {
enabled: true,
agents: ["main"],
modelFallback: "google/gemini-3-flash",
modelFallbackPolicy: "resolved-only",
},
});
expect(result.ok).toBe(true);
});
it("accepts custom toolsAllow entries", () => {
const result = validateJsonSchemaValue({
schema: manifest.configSchema,
cacheKey: "active-memory.manifest.tools-allow",
value: {
enabled: true,
agents: ["main"],
toolsAllow: ["lcm_grep", "lcm_describe", "lcm_expand_query"],
},
});
expect(result.ok).toBe(true);
});
it("rejects wildcard and group toolsAllow entries", () => {
const result = validateJsonSchemaValue({
schema: manifest.configSchema,
cacheKey: "active-memory.manifest.tools-allow.reserved",
value: {
enabled: true,
agents: ["main"],
toolsAllow: ["*", "group:plugins"],
},
});
expect(result.ok).toBe(false);
});
it("accepts timeoutMs values at the runtime ceiling", () => {
const result = validateJsonSchemaValue({
schema: manifest.configSchema,
cacheKey: "active-memory.manifest.timeout-ceiling",
value: {
enabled: true,
agents: ["main"],
timeoutMs: 120_000,
},
});
expect(result.ok).toBe(true);
});
it("accepts setupGraceTimeoutMs values at the runtime ceiling", () => {
const result = validateJsonSchemaValue({
schema: manifest.configSchema,
cacheKey: "active-memory.manifest.setup-grace-timeout-ceiling",
value: {
enabled: true,
agents: ["main"],
setupGraceTimeoutMs: 30_000,
},
});
expect(result.ok).toBe(true);
});
it("accepts explicit in allowedChatTypes", () => {
const result = validateJsonSchemaValue({
schema: manifest.configSchema,
cacheKey: "active-memory.manifest.allowed-chat-types.explicit",
value: {
enabled: true,
agents: ["main"],
allowedChatTypes: ["direct", "explicit"],
},
});
expect(result.ok).toBe(true);
});
it("rejects timeoutMs values above the runtime ceiling", () => {
const result = validateJsonSchemaValue({
schema: manifest.configSchema,
cacheKey: "active-memory.manifest.timeout-above-ceiling",
value: {
enabled: true,
agents: ["main"],
timeoutMs: 120_001,
},
});
expect(result.ok).toBe(false);
});
it("rejects setupGraceTimeoutMs values above the runtime ceiling", () => {
const result = validateJsonSchemaValue({
schema: manifest.configSchema,
cacheKey: "active-memory.manifest.setup-grace-timeout-above-ceiling",
value: {
enabled: true,
agents: ["main"],
setupGraceTimeoutMs: 30_001,
},
});
expect(result.ok).toBe(false);
});
it("rejects unknown allowedChatTypes values", () => {
const result = validateJsonSchemaValue({
schema: manifest.configSchema,
cacheKey: "active-memory.manifest.allowed-chat-types.invalid",
value: {
enabled: true,
agents: ["main"],
allowedChatTypes: ["direct", "portal"],
},
});
expect(result.ok).toBe(false);
});
});

View File

@@ -0,0 +1,100 @@
// Active Memory tests cover doctor contract api plugin behavior.
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import {
createPluginStateKeyedStoreForTests,
resetPluginStateStoreForTests,
} from "openclaw/plugin-sdk/plugin-state-test-runtime";
import type {
OpenKeyedStoreOptions,
PluginDoctorStateMigrationContext,
} from "openclaw/plugin-sdk/runtime-doctor";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { stateMigrations } from "./doctor-contract-api.js";
function createDoctorContext(env: NodeJS.ProcessEnv): PluginDoctorStateMigrationContext {
return {
openPluginStateKeyedStore<T>(options: OpenKeyedStoreOptions) {
return createPluginStateKeyedStoreForTests<T>("active-memory", {
...options,
env: options.env ?? env,
});
},
};
}
describe("active-memory doctor state migration", () => {
let stateDir = "";
let env: NodeJS.ProcessEnv;
beforeEach(async () => {
resetPluginStateStoreForTests();
stateDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-active-memory-doctor-"));
env = { ...process.env, OPENCLAW_STATE_DIR: stateDir };
});
afterEach(async () => {
await fs.rm(stateDir, { recursive: true, force: true });
});
it("imports legacy session opt-outs into plugin state", async () => {
const sourcePath = path.join(stateDir, "plugins", "active-memory", "session-toggles.json");
await fs.mkdir(path.dirname(sourcePath), { recursive: true });
await fs.writeFile(
sourcePath,
JSON.stringify({
sessions: {
"telegram:dm:123": { disabled: true, updatedAt: 1700 },
"telegram:dm:456": { disabled: false, updatedAt: 1701 },
},
}),
);
const migration = stateMigrations[0];
await expect(
migration.detectLegacyState({
config: {},
env,
stateDir,
oauthDir: path.join(stateDir, "oauth"),
context: createDoctorContext(env),
}),
).resolves.toMatchObject({
preview: [expect.stringContaining("1 entry")],
});
const result = await migration.migrateLegacyState({
config: {},
env,
stateDir,
oauthDir: path.join(stateDir, "oauth"),
context: createDoctorContext(env),
});
expect(result.warnings).toEqual([]);
expect(result.changes).toEqual([
expect.stringContaining("Migrated 1 Active Memory session toggle entry"),
expect.stringContaining("Archived Active Memory session toggles legacy source"),
]);
await expect(fs.access(sourcePath)).rejects.toThrow();
await expect(fs.access(`${sourcePath}.migrated`)).resolves.toBeUndefined();
const entries = await createDoctorContext(env)
.openPluginStateKeyedStore({
namespace: "session-toggles",
maxEntries: 10_000,
})
.entries();
expect(entries).toMatchObject([
{
key: expect.any(String),
value: {
sessionKey: "telegram:dm:123",
disabled: true,
updatedAt: 1700,
},
},
]);
});
});

View File

@@ -0,0 +1,124 @@
/**
* Doctor migration contract for Active Memory state. It moves legacy per-session
* toggle JSON into the plugin state keyed store used by current runtimes.
*/
import crypto from "node:crypto";
import fs from "node:fs/promises";
import path from "node:path";
import {
archiveLegacyStateSource,
type PluginDoctorStateMigration,
} from "openclaw/plugin-sdk/runtime-doctor";
type ActiveMemoryToggleEntry = {
sessionKey: string;
disabled: boolean;
updatedAt: number;
};
const TOGGLE_STATE_FILE = "session-toggles.json";
const SESSION_TOGGLES_NAMESPACE = "session-toggles";
const MAX_TOGGLE_ENTRIES = 10_000;
function resolveToggleStatePath(stateDir: string): string {
return path.join(stateDir, "plugins", "active-memory", TOGGLE_STATE_FILE);
}
function activeMemoryToggleKey(sessionKey: string): string {
return crypto.createHash("sha256").update(sessionKey, "utf8").digest("hex");
}
async function readLegacyToggleEntries(filePath: string): Promise<ActiveMemoryToggleEntry[]> {
try {
const parsed = JSON.parse(await fs.readFile(filePath, "utf8")) as unknown;
if (!parsed || typeof parsed !== "object") {
return [];
}
const sessions = (parsed as { sessions?: unknown }).sessions;
if (!sessions || typeof sessions !== "object" || Array.isArray(sessions)) {
return [];
}
const entries: ActiveMemoryToggleEntry[] = [];
for (const [sessionKey, value] of Object.entries(sessions)) {
if (!sessionKey.trim() || !value || typeof value !== "object" || Array.isArray(value)) {
continue;
}
if ((value as { disabled?: unknown }).disabled !== true) {
continue;
}
const updatedAt =
typeof (value as { updatedAt?: unknown }).updatedAt === "number"
? (value as { updatedAt: number }).updatedAt
: Date.now();
entries.push({ sessionKey, disabled: true, updatedAt });
}
return entries;
} catch {
return [];
}
}
/** State migrations exposed to OpenClaw doctor for Active Memory. */
export const stateMigrations: PluginDoctorStateMigration[] = [
{
id: "active-memory-session-toggles-json-to-plugin-state",
label: "Active Memory session toggles",
async detectLegacyState(params) {
const filePath = resolveToggleStatePath(params.stateDir);
const entries = await readLegacyToggleEntries(filePath);
if (entries.length === 0) {
return null;
}
return {
preview: [
`- Active Memory session toggles: ${entries.length} ${entries.length === 1 ? "entry" : "entries"} -> plugin state (${SESSION_TOGGLES_NAMESPACE})`,
],
};
},
async migrateLegacyState(params) {
const changes: string[] = [];
const warnings: string[] = [];
const filePath = resolveToggleStatePath(params.stateDir);
const entries = await readLegacyToggleEntries(filePath);
if (entries.length === 0) {
return { changes, warnings };
}
const store = params.context.openPluginStateKeyedStore<ActiveMemoryToggleEntry>({
namespace: SESSION_TOGGLES_NAMESPACE,
maxEntries: MAX_TOGGLE_ENTRIES,
});
const existingKeys = new Set((await store.entries()).map((entry) => entry.key));
const missingEntries = entries.filter(
(entry) => !existingKeys.has(activeMemoryToggleKey(entry.sessionKey)),
);
if (missingEntries.length > MAX_TOGGLE_ENTRIES - existingKeys.size) {
warnings.push(
`Skipped Active Memory session toggle migration because plugin state has room for ${MAX_TOGGLE_ENTRIES - existingKeys.size} of ${missingEntries.length} missing entries; left legacy source in place`,
);
return { changes, warnings };
}
let imported = 0;
for (const entry of entries) {
const key = activeMemoryToggleKey(entry.sessionKey);
if (existingKeys.has(key)) {
continue;
}
await store.register(key, entry);
existingKeys.add(key);
imported++;
}
if (imported > 0) {
changes.push(
`Migrated ${imported} Active Memory session toggle ${imported === 1 ? "entry" : "entries"} -> plugin state`,
);
}
await archiveLegacyStateSource({
filePath,
label: "Active Memory session toggles",
changes,
warnings,
});
return { changes, warnings };
},
},
];

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,186 @@
{
"id": "active-memory",
"activation": {
"onStartup": true
},
"name": "Active Memory",
"description": "Runs a bounded blocking memory sub-agent before eligible conversational replies and injects relevant memory into prompt context.",
"configSchema": {
"type": "object",
"additionalProperties": false,
"properties": {
"enabled": { "type": "boolean" },
"agents": {
"type": "array",
"items": { "type": "string" }
},
"model": { "type": "string" },
"modelFallback": { "type": "string" },
"modelFallbackPolicy": {
"type": "string",
"enum": ["default-remote", "resolved-only"]
},
"allowedChatTypes": {
"type": "array",
"items": {
"type": "string",
"enum": ["direct", "group", "channel", "explicit"]
}
},
"allowedChatIds": {
"type": "array",
"items": { "type": "string" }
},
"deniedChatIds": {
"type": "array",
"items": { "type": "string" }
},
"thinking": {
"type": "string",
"enum": ["off", "minimal", "low", "medium", "high", "xhigh", "adaptive"]
},
"timeoutMs": { "type": "integer", "minimum": 250, "maximum": 120000 },
"setupGraceTimeoutMs": { "type": "integer", "minimum": 0, "maximum": 30000 },
"queryMode": {
"type": "string",
"enum": ["message", "recent", "full"]
},
"promptStyle": {
"type": "string",
"enum": [
"balanced",
"strict",
"contextual",
"recall-heavy",
"precision-heavy",
"preference-only"
]
},
"toolsAllow": {
"type": "array",
"items": {
"type": "string",
"pattern": "^(?!\\*$)(?![Gg][Rr][Oo][Uu][Pp]:).+"
},
"maxItems": 32
},
"promptOverride": { "type": "string" },
"promptAppend": { "type": "string" },
"maxSummaryChars": { "type": "integer", "minimum": 40, "maximum": 1000 },
"recentUserTurns": { "type": "integer", "minimum": 0, "maximum": 4 },
"recentAssistantTurns": { "type": "integer", "minimum": 0, "maximum": 3 },
"recentUserChars": { "type": "integer", "minimum": 40, "maximum": 1000 },
"recentAssistantChars": { "type": "integer", "minimum": 40, "maximum": 1000 },
"logging": { "type": "boolean" },
"persistTranscripts": { "type": "boolean" },
"transcriptDir": { "type": "string" },
"cacheTtlMs": { "type": "integer", "minimum": 1000, "maximum": 120000 },
"circuitBreakerMaxTimeouts": { "type": "integer", "minimum": 1, "maximum": 20 },
"circuitBreakerCooldownMs": { "type": "integer", "minimum": 5000, "maximum": 600000 },
"qmd": {
"type": "object",
"additionalProperties": false,
"properties": {
"searchMode": {
"type": "string",
"enum": ["inherit", "search", "vsearch", "query"]
}
}
}
}
},
"uiHints": {
"enabled": {
"label": "Active Memory Recall",
"help": "Globally enable or pause Active Memory recall while keeping the plugin command available."
},
"agents": {
"label": "Target Agents",
"help": "Explicit agent ids that may use active memory."
},
"model": {
"label": "Memory Model",
"help": "Provider/model used for the blocking memory sub-agent."
},
"modelFallback": {
"label": "Fallback Memory Model",
"help": "Optional provider/model to use if no explicit plugin model, session model, or agent primary model resolves."
},
"modelFallbackPolicy": {
"label": "Model Fallback Policy",
"help": "Deprecated compatibility field. modelFallback is only the chain-resolution last resort when no explicit plugin model, session model, or agent primary model resolves; it is not runtime failover."
},
"allowedChatTypes": {
"label": "Allowed Chat Types",
"help": "Choose which session types may run Active Memory. Defaults to direct-message style sessions only, but explicit portal/webchat sessions can also be enabled."
},
"allowedChatIds": {
"label": "Allowed Chat IDs",
"help": "Optional explicit allowlist of chat/user IDs (e.g. Feishu chat_id oc_xxx, open_id ou_xxx, Telegram chat id, Slack channel id). When non-empty, Active Memory only runs for sessions whose conversation id is in the list, across **every** chat type at once (direct, group, channel). Setting this narrows every allowed chat type simultaneously — if you want 'all directs + only specific groups', use allowedChatTypes: ['group'] + allowedChatIds: [<group ids>] and rely on direct chats being matched via the direct session id (e.g. the user's open_id) instead. Leave empty to fall back to allowedChatTypes alone."
},
"deniedChatIds": {
"label": "Denied Chat IDs",
"help": "Optional explicit denylist of chat/user IDs. Sessions whose resolved conversation id matches the list are skipped even when the chat type is allowed. Applied after allowedChatIds."
},
"timeoutMs": {
"label": "Timeout (ms)",
"help": "Recall work budget on the main lane. Before recall, the hook allows up to 1500 ms for session/config preflight. After recall starts, it reserves another fixed 1500 ms only for abort settlement and transcript recovery."
},
"setupGraceTimeoutMs": {
"label": "Setup Grace Timeout (ms)",
"help": "Advanced: extra recall-work budget for cold embedded-run setup. Defaults to 0. The separate 1500 ms preflight cap and 1500 ms post-recall completion allowance still apply."
},
"queryMode": {
"label": "Query Mode",
"help": "Choose whether the blocking memory sub-agent sees only the latest user message, a small recent tail, or the full conversation."
},
"promptStyle": {
"label": "Prompt Style",
"help": "Choose how eager or strict the blocking memory sub-agent should be when deciding whether to return memory."
},
"toolsAllow": {
"label": "Allowed Memory Tools",
"help": "Advanced: tool names the blocking memory sub-agent may use. Defaults to memory_search and memory_get, or memory_recall when plugins.slots.memory selects memory-lancedb; configure this for other non-core memory providers. Wildcards, group entries, and core agent tools are ignored."
},
"thinking": {
"label": "Thinking Override",
"help": "Advanced: optional thinking level for the blocking memory sub-agent. Defaults to off for speed."
},
"promptOverride": {
"label": "Prompt Override",
"help": "Advanced: replace the default Active Memory sub-agent instructions. Conversation context is still appended."
},
"promptAppend": {
"label": "Prompt Append",
"help": "Advanced: append extra operator instructions after the default Active Memory sub-agent instructions."
},
"maxSummaryChars": {
"label": "Max Summary Characters",
"help": "Maximum total characters allowed in the active-memory summary."
},
"logging": {
"label": "Enable Logging",
"help": "Emit active memory timing and result logs."
},
"circuitBreakerMaxTimeouts": {
"label": "Circuit Breaker Max Timeouts",
"help": "Skip recall after this many consecutive timeouts for the same agent/model. Resets on a successful recall or after the cooldown expires. Default: 3."
},
"circuitBreakerCooldownMs": {
"label": "Circuit Breaker Cooldown (ms)",
"help": "How long to skip recall after the circuit breaker trips, in milliseconds. Default: 60000 (1 minute)."
},
"persistTranscripts": {
"label": "Persist Transcripts",
"help": "Keep blocking memory sub-agent session transcripts on disk in a separate plugin-owned directory."
},
"transcriptDir": {
"label": "Transcript Directory",
"help": "Relative directory under the agent sessions folder used when transcript persistence is enabled."
},
"qmd.searchMode": {
"label": "QMD Search Mode",
"help": "Override the QMD search mode used by the blocking memory sub-agent. Defaults to fast lexical search; use inherit to match the main memory backend setting."
}
}
}