@parkgogogo/openclaw-reflection 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/INSTALL.md ADDED
@@ -0,0 +1,78 @@
1
+ # OpenClaw Install Guide
2
+
3
+ This file is for OpenClaw itself to follow when installing `@parkgogogo/openclaw-reflection`.
4
+
5
+ ## Goal
6
+
7
+ Install the packaged plugin, enable it under the `openclaw-reflection` plugin id, provide a valid LLM config, and restart the gateway so the plugin becomes active.
8
+
9
+ ## Install From npm
10
+
11
+ Use the published package, not a linked workspace checkout:
12
+
13
+ ```bash
14
+ openclaw plugins install @parkgogogo/openclaw-reflection@0.1.0
15
+ ```
16
+
17
+ If you are installing from a local release artifact instead:
18
+
19
+ ```bash
20
+ openclaw plugins install ./parkgogogo-openclaw-reflection-0.1.0.tgz
21
+ ```
22
+
23
+ ## Write Config
24
+
25
+ Add this under `plugins.entries.openclaw-reflection` in the active OpenClaw profile config:
26
+
27
+ ```json
28
+ {
29
+ "enabled": true,
30
+ "config": {
31
+ "workspaceDir": "/absolute/path/to/agent-workspace",
32
+ "bufferSize": 50,
33
+ "logLevel": "info",
34
+ "llm": {
35
+ "baseURL": "https://api.openai.com/v1",
36
+ "apiKey": "YOUR_API_KEY",
37
+ "model": "gpt-4.1-mini"
38
+ },
39
+ "memoryGate": {
40
+ "enabled": true,
41
+ "windowSize": 10
42
+ },
43
+ "consolidation": {
44
+ "enabled": true,
45
+ "schedule": "0 2 * * *"
46
+ }
47
+ }
48
+ }
49
+ ```
50
+
51
+ Make sure `plugins.allow` includes `openclaw-reflection` if the profile uses an allowlist.
52
+
53
+ ## Validate And Restart
54
+
55
+ Run:
56
+
57
+ ```bash
58
+ openclaw config validate --json
59
+ openclaw gateway run --force --verbose
60
+ ```
61
+
62
+ In another shell, confirm the gateway is live:
63
+
64
+ ```bash
65
+ curl --fail http://127.0.0.1:PORT/health
66
+ ```
67
+
68
+ ## Expected Success Signals
69
+
70
+ - The gateway starts without plugin registration errors
71
+ - The plugin log contains `Plugin registered successfully, all hooks active`
72
+ - `GET /health` returns JSON containing `"ok": true`
73
+
74
+ ## Do Not Do This
75
+
76
+ - Do not use `openclaw plugins install -l` unless you are actively developing this repository
77
+ - Do not leave `llm.baseURL`, `llm.apiKey`, or `llm.model` empty
78
+ - Do not configure the plugin under the old id `reflection-plugin`
package/README.md ADDED
@@ -0,0 +1,195 @@
1
+ # OpenClaw Reflection
2
+
3
+ ![OpenClaw Plugin](https://img.shields.io/badge/OpenClaw-Plugin-111111?style=flat-square)
4
+ ![TypeScript](https://img.shields.io/badge/TypeScript-5.x-3178c6?style=flat-square)
5
+ ![memoryGate 16/16](https://img.shields.io/badge/memoryGate-16%2F16%20passed-2ea043?style=flat-square)
6
+ ![writer guardian 16/16](https://img.shields.io/badge/writer%20guardian-16%2F16%20passed-2ea043?style=flat-square)
7
+
8
+ **Make OpenClaw's native memory system sharper without replacing it.**
9
+
10
+ OpenClaw Reflection is an additive layer on top of OpenClaw's built-in Markdown memory system. It captures message flow, keeps thread noise out of long-term memory, writes durable knowledge into the same human-readable memory files OpenClaw already uses, and periodically consolidates them so your agent gets sharper over time instead of messier.
11
+
12
+ ## Built On OpenClaw Memory
13
+
14
+ OpenClaw memory is already workspace-native: the source of truth is Markdown files in the agent workspace, not a hidden database. In the official model, daily logs live under `memory/YYYY-MM-DD.md`, while `MEMORY.md` is the curated long-term layer.
15
+
16
+ Reflection builds on top of that system instead of replacing it.
17
+
18
+ - It does **not** introduce a separate memory store
19
+ - It does **not** require replacing OpenClaw's default `memory-core`
20
+ - It does **not** take over the active `plugins.slots.memory` role
21
+ - It works by listening to message hooks and curating the same workspace memory files
22
+
23
+ In practice, that means low migration risk and low conceptual overhead: you keep OpenClaw's native MEMORY workflow, and Reflection enhances the capture, filtering, routing, and consolidation steps around it.
24
+
25
+ ## Why People Install It
26
+
27
+ Most chat memory systems fail in one of two ways:
28
+
29
+ - they forget too much, so you keep re-explaining the same context
30
+ - they remember too much, so temporary thread noise pollutes long-term memory
31
+
32
+ Reflection is built to fix both.
33
+
34
+ - Keep stable user preferences and collaboration habits
35
+ - Preserve durable shared context across sessions
36
+ - Separate memory into `MEMORY.md`, `USER.md`, `SOUL.md`, `IDENTITY.md`, and `TOOLS.md`
37
+ - Refuse one-off tasks, active thread chatter, and misrouted writes
38
+ - Periodically consolidate memory so it stays usable
39
+
40
+ ## Install
41
+
42
+ ### Recommended for users: install the plugin package
43
+
44
+ OpenClaw can install plugins directly from a package source. That is the right distribution path for Reflection, because users should not need to clone the repository or run `pnpm install` just to use the plugin.
45
+
46
+ For a step-by-step installation flow that OpenClaw can follow directly, see [INSTALL.md](./INSTALL.md).
47
+
48
+ Registry install after publishing:
49
+
50
+ ```bash
51
+ openclaw plugins install <npm-spec>
52
+ ```
53
+
54
+ Example:
55
+
56
+ ```bash
57
+ openclaw plugins install @parkgogogo/openclaw-reflection
58
+ ```
59
+
60
+ ### Add the plugin config
61
+
62
+ Put the following under `plugins.entries.openclaw-reflection` in your OpenClaw config:
63
+
64
+ ```json
65
+ {
66
+ "enabled": true,
67
+ "config": {
68
+ "workspaceDir": "/absolute/path/to/your-agent-workspace",
69
+ "bufferSize": 50,
70
+ "logLevel": "info",
71
+ "llm": {
72
+ "baseURL": "https://api.openai.com/v1",
73
+ "apiKey": "YOUR_API_KEY",
74
+ "model": "gpt-4.1-mini"
75
+ },
76
+ "memoryGate": {
77
+ "enabled": true,
78
+ "windowSize": 10
79
+ },
80
+ "consolidation": {
81
+ "enabled": true,
82
+ "schedule": "0 2 * * *"
83
+ }
84
+ }
85
+ }
86
+ ```
87
+
88
+ ### Restart OpenClaw Gateway
89
+
90
+ Once the gateway restarts, Reflection will begin listening to `message_received` and `before_message_write`, then writing curated memory files into your configured `workspaceDir`.
91
+
92
+ ## What You Get
93
+
94
+ | You want | Reflection gives you |
95
+ | ------------------------------------ | ---------------------------------------------------------- |
96
+ | A memory system you can inspect | Plain Markdown files you can open, edit, diff, and version |
97
+ | Better continuity across sessions | Durable facts routed into the right long-term file |
98
+ | Less memory pollution | Gatekeeping that refuses temporary or misrouted content |
99
+ | A system that stays usable over time | Scheduled consolidation for existing memory files |
100
+
101
+ ## Why This Beats Naive Memory
102
+
103
+ | Naive memory | Reflection |
104
+ | -------------------------------- | ------------------------------------------------ |
105
+ | Appends whatever seems memorable | Filters for durable signal before writing |
106
+ | Hides memory in a black box | Stores memory in readable Markdown files |
107
+ | Mixes all facts together | Routes facts into purpose-specific files |
108
+ | Lets bad writes accumulate | Adds writer guarding and scheduled consolidation |
109
+
110
+ ## How It Works
111
+
112
+ ```mermaid
113
+ flowchart LR
114
+ A["Incoming conversation"] --> B["Session buffer"]
115
+ B --> C["memoryGate"]
116
+ C -->|durable fact| D["Writer guardian"]
117
+ C -->|thread noise| E["No write"]
118
+ D --> F["MEMORY.md / USER.md / SOUL.md / IDENTITY.md / TOOLS.md"]
119
+ F --> G["Scheduled consolidation"]
120
+ ```
121
+
122
+ In practice, the pipeline is simple:
123
+
124
+ 1. Reflection captures conversation context from OpenClaw hooks.
125
+ 2. `memoryGate` decides whether the candidate fact is durable enough to keep.
126
+ 3. A file-specific guardian either rewrites the target memory file or refuses the write.
127
+ 4. Scheduled consolidation keeps `MEMORY.md`, `USER.md`, `SOUL.md`, and `TOOLS.md` compact over time.
128
+
129
+ ## Proof, Not Just Promises
130
+
131
+ This repo already includes offline eval coverage for the two hardest parts of the system:
132
+
133
+ - [`memoryGate`: 16/16 passed on V2](./evals/results/2026-03-08-memory-gate-v2-16-of-16.md)
134
+ - [`writer guardian`: 16/16 passed on V2](./evals/results/2026-03-08-writer-guardian-v2-16-of-16.md)
135
+
136
+ These evals focus on the failure modes that make long-term memory systems unreliable:
137
+
138
+ - refusing active thread noise
139
+ - keeping user facts out of the wrong file
140
+ - preserving `SOUL` continuity rules
141
+ - replacing outdated `IDENTITY` metadata correctly
142
+ - keeping local tool mappings in `TOOLS.md` without turning it into a tool registry
143
+
144
+ ## The Memory Files
145
+
146
+ | File | Purpose |
147
+ | ------------- | ----------------------------------------------------------------------------------- |
148
+ | `MEMORY.md` | Durable shared context, important conclusions, long-lived background facts |
149
+ | `USER.md` | Stable user preferences, collaboration style, helpful personal context |
150
+ | `SOUL.md` | Assistant principles, boundaries, and continuity rules |
151
+ | `IDENTITY.md` | Explicit identity metadata such as name, vibe, or avatar-style descriptors |
152
+ | `TOOLS.md` | Environment-specific tool aliases, endpoints, device names, and local tool mappings |
153
+
154
+ ## Advanced Configuration
155
+
156
+ | Key | Default | Meaning |
157
+ | ------------------------ | --------------------------- | ----------------------------------------- |
158
+ | `workspaceDir` | none | Directory where memory files are written |
159
+ | `bufferSize` | `50` | Session buffer size |
160
+ | `logLevel` | `info` | `debug`, `info`, `warn`, or `error` |
161
+ | `llm.baseURL` | `https://api.openai.com/v1` | OpenAI-compatible provider URL |
162
+ | `llm.apiKey` | empty | Provider API key |
163
+ | `llm.model` | `gpt-4.1-mini` | Model used for analysis and consolidation |
164
+ | `memoryGate.enabled` | `true` | Enable long-term memory filtering |
165
+ | `memoryGate.windowSize` | `10` | Message window used during analysis |
166
+ | `consolidation.enabled` | `true` | Enable scheduled consolidation |
167
+ | `consolidation.schedule` | `0 2 * * *` | Cron expression for consolidation |
168
+
169
+ ## Built For
170
+
171
+ - personal agents that should get better over weeks, not just one session
172
+ - teams that want memory with reviewability and version control
173
+ - OpenClaw users who do not want a black-box memory store
174
+ - agents that need stronger continuity without turning every chat into permanent history
175
+
176
+ ## Development And Evals
177
+
178
+ ```bash
179
+ pnpm run typecheck
180
+ pnpm run eval:memory-gate
181
+ pnpm run eval:writer-guardian
182
+ pnpm run eval:all
183
+ ```
184
+
185
+ More eval details: [evals/README.md](./evals/README.md)
186
+
187
+ Fast packaged-plugin regression on a reused local OpenClaw profile:
188
+
189
+ ```bash
190
+ pnpm run e2e:openclaw-plugin
191
+ ```
192
+
193
+ ## Links
194
+
195
+ - OpenClaw plugin docs: [docs.openclaw.ai/tools/plugin](https://docs.openclaw.ai/tools/plugin)
@@ -0,0 +1,67 @@
1
+ {
2
+ "id": "openclaw-reflection",
3
+ "entry": "src/index.ts",
4
+ "configSchema": {
5
+ "type": "object",
6
+ "properties": {
7
+ "workspaceDir": {
8
+ "type": "string",
9
+ "description": "Directory used for USER.md, MEMORY.md, SOUL.md, and IDENTITY.md writes"
10
+ },
11
+ "bufferSize": {
12
+ "type": "integer",
13
+ "minimum": 1,
14
+ "default": 50
15
+ },
16
+ "logLevel": {
17
+ "type": "string",
18
+ "enum": ["debug", "info", "warn", "error"],
19
+ "default": "info"
20
+ },
21
+ "llm": {
22
+ "type": "object",
23
+ "properties": {
24
+ "baseURL": {
25
+ "type": "string",
26
+ "default": "https://api.openai.com/v1"
27
+ },
28
+ "apiKey": {
29
+ "type": "string",
30
+ "default": ""
31
+ },
32
+ "model": {
33
+ "type": "string",
34
+ "default": "gpt-4.1-mini"
35
+ }
36
+ }
37
+ },
38
+ "memoryGate": {
39
+ "type": "object",
40
+ "properties": {
41
+ "enabled": {
42
+ "type": "boolean",
43
+ "default": true
44
+ },
45
+ "windowSize": {
46
+ "type": "integer",
47
+ "minimum": 1,
48
+ "default": 10
49
+ }
50
+ }
51
+ },
52
+ "consolidation": {
53
+ "type": "object",
54
+ "properties": {
55
+ "enabled": {
56
+ "type": "boolean",
57
+ "default": true
58
+ },
59
+ "schedule": {
60
+ "type": "string",
61
+ "default": "0 2 * * *"
62
+ }
63
+ }
64
+ }
65
+ }
66
+ }
67
+ }
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@parkgogogo/openclaw-reflection",
3
+ "version": "0.1.0",
4
+ "description": "OpenClaw plugin that enhances native Markdown memory with filtering, curation, and consolidation",
5
+ "type": "module",
6
+ "main": "src/index.ts",
7
+ "files": [
8
+ "src/",
9
+ "openclaw.plugin.json",
10
+ "README.md",
11
+ "INSTALL.md"
12
+ ],
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/parkgogogo/openclaw-reflection.git"
16
+ },
17
+ "homepage": "https://github.com/parkgogogo/openclaw-reflection#readme",
18
+ "bugs": {
19
+ "url": "https://github.com/parkgogogo/openclaw-reflection/issues"
20
+ },
21
+ "scripts": {
22
+ "build": "tsc --noEmit",
23
+ "clean": "rm -rf logs",
24
+ "typecheck": "tsc --noEmit",
25
+ "e2e:openclaw-plugin": "bash scripts/e2e-openclaw-plugin.sh",
26
+ "eval:memory-gate": "pnpm exec tsc && node evals/run.mjs --suite memory-gate",
27
+ "eval:writer-guardian": "pnpm exec tsc && node evals/run.mjs --suite writer-guardian",
28
+ "eval:all": "pnpm exec tsc && node evals/run.mjs --suite all"
29
+ },
30
+ "keywords": [
31
+ "openclaw",
32
+ "plugin",
33
+ "reflection",
34
+ "message-buffer"
35
+ ],
36
+ "author": "",
37
+ "license": "MIT",
38
+ "openclaw": {
39
+ "extensions": [
40
+ "./src/index.ts"
41
+ ]
42
+ },
43
+ "dependencies": {
44
+ "proper-lockfile": "^4.1.2",
45
+ "ulid": "^2.3.0"
46
+ },
47
+ "devDependencies": {
48
+ "@types/node": "^20.0.0",
49
+ "typescript": "^5.0.0"
50
+ },
51
+ "packageManager": "pnpm@8.6.9+sha512.2cf11a086be557875519e25e1ea8bfa4247f4844e8a2a99272fdb072bd204ea19479ba64b75c3d4c8117d1ac0b3212cedbda7ba5c5dfcf964ee7d07bd139dcd3"
52
+ }
package/src/buffer.ts ADDED
@@ -0,0 +1,40 @@
1
+ export class CircularBuffer<T> {
2
+ private buffer: T[];
3
+ private capacity: number;
4
+
5
+ constructor(capacity: number) {
6
+ if (!Number.isInteger(capacity) || capacity <= 0) {
7
+ throw new Error("CircularBuffer capacity must be a positive integer");
8
+ }
9
+
10
+ this.capacity = capacity;
11
+ this.buffer = [];
12
+ }
13
+
14
+ push(item: T): T | null {
15
+ let evicted: T | null = null;
16
+
17
+ if (this.isFull()) {
18
+ evicted = this.buffer.shift() ?? null;
19
+ }
20
+
21
+ this.buffer.push(item);
22
+ return evicted;
23
+ }
24
+
25
+ toArray(): T[] {
26
+ return [...this.buffer];
27
+ }
28
+
29
+ isFull(): boolean {
30
+ return this.buffer.length >= this.capacity;
31
+ }
32
+
33
+ size(): number {
34
+ return this.buffer.length;
35
+ }
36
+
37
+ clear(): void {
38
+ this.buffer = [];
39
+ }
40
+ }
package/src/config.ts ADDED
@@ -0,0 +1,254 @@
1
+ import path from "node:path";
2
+ import type { LogLevel, PluginConfig } from "./types.js";
3
+
4
+ interface PluginAPI {
5
+ pluginConfig?: unknown;
6
+ config?: {
7
+ get?: (key: string) => unknown;
8
+ };
9
+ }
10
+
11
+ const DEFAULT_CONFIG: PluginConfig = {
12
+ bufferSize: 50,
13
+ logLevel: "info",
14
+ llm: {
15
+ baseURL: "https://api.openai.com/v1",
16
+ apiKey: "",
17
+ model: "gpt-4.1-mini",
18
+ },
19
+ memoryGate: {
20
+ enabled: true,
21
+ windowSize: 10,
22
+ },
23
+ consolidation: {
24
+ enabled: true,
25
+ schedule: "0 2 * * *",
26
+ },
27
+ };
28
+
29
+ const VALID_LOG_LEVELS = new Set<LogLevel>(["debug", "info", "warn", "error"]);
30
+
31
+ function getPositiveInteger(value: unknown, fallback: number): number {
32
+ if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
33
+ return fallback;
34
+ }
35
+
36
+ return value;
37
+ }
38
+
39
+ function getBoolean(value: unknown, fallback: boolean): boolean {
40
+ if (typeof value !== "boolean") {
41
+ return fallback;
42
+ }
43
+
44
+ return value;
45
+ }
46
+
47
+ function getString(value: unknown, fallback: string): string {
48
+ if (typeof value !== "string" || value.trim().length === 0) {
49
+ return fallback;
50
+ }
51
+
52
+ return value;
53
+ }
54
+
55
+ function isRecord(value: unknown): value is Record<string, unknown> {
56
+ return typeof value === "object" && value !== null;
57
+ }
58
+
59
+ function getLogLevel(value: unknown): LogLevel {
60
+ if (typeof value === "string" && VALID_LOG_LEVELS.has(value as LogLevel)) {
61
+ return value as LogLevel;
62
+ }
63
+
64
+ return DEFAULT_CONFIG.logLevel;
65
+ }
66
+
67
+ function readConfigValue(
68
+ config: PluginAPI["config"],
69
+ key: string
70
+ ): unknown {
71
+ const directValue = config?.get?.(key);
72
+ if (directValue !== undefined) {
73
+ return directValue;
74
+ }
75
+
76
+ const segments = key.split(".");
77
+ if (segments.length === 1) {
78
+ return undefined;
79
+ }
80
+
81
+ const [rootKey, ...nestedSegments] = segments;
82
+ let currentValue = config?.get?.(rootKey);
83
+
84
+ for (const segment of nestedSegments) {
85
+ if (!isRecord(currentValue) || !(segment in currentValue)) {
86
+ return undefined;
87
+ }
88
+
89
+ currentValue = currentValue[segment];
90
+ }
91
+
92
+ return currentValue;
93
+ }
94
+
95
+ function readRecordValue(
96
+ value: unknown,
97
+ key: string
98
+ ): unknown {
99
+ const segments = key.split(".");
100
+ let currentValue: unknown = value;
101
+
102
+ for (const segment of segments) {
103
+ if (!isRecord(currentValue) || !(segment in currentValue)) {
104
+ return undefined;
105
+ }
106
+
107
+ currentValue = currentValue[segment];
108
+ }
109
+
110
+ return currentValue;
111
+ }
112
+
113
+ function readPluginConfigValue(api: PluginAPI, key: string): unknown {
114
+ const pluginConfigValue = readRecordValue(api.pluginConfig, key);
115
+ if (pluginConfigValue !== undefined) {
116
+ return pluginConfigValue;
117
+ }
118
+
119
+ return readConfigValue(api.config, key);
120
+ }
121
+
122
+ export type ConfigLogSnapshot = Record<string, unknown> & {
123
+ bufferSize: number;
124
+ logLevel: LogLevel;
125
+ llm: {
126
+ baseURL: string;
127
+ apiKeyConfigured: boolean;
128
+ model: string;
129
+ };
130
+ memoryGate: {
131
+ enabled: boolean;
132
+ windowSize: number;
133
+ };
134
+ consolidation: {
135
+ enabled: boolean;
136
+ schedule: string;
137
+ };
138
+ };
139
+
140
+ export interface WorkspaceResolution {
141
+ workspaceDir?: string;
142
+ source: string;
143
+ reason?: string;
144
+ }
145
+
146
+ function getNonEmptyString(value: unknown): string | undefined {
147
+ if (typeof value !== "string") {
148
+ return undefined;
149
+ }
150
+
151
+ const trimmed = value.trim();
152
+ return trimmed.length > 0 ? trimmed : undefined;
153
+ }
154
+
155
+ function isFilesystemRoot(dirPath: string): boolean {
156
+ const resolved = path.resolve(dirPath);
157
+ return resolved === path.parse(resolved).root;
158
+ }
159
+
160
+ export function createConfigLogSnapshot(
161
+ config: PluginConfig
162
+ ): ConfigLogSnapshot {
163
+ return {
164
+ bufferSize: config.bufferSize,
165
+ logLevel: config.logLevel,
166
+ llm: {
167
+ baseURL: config.llm.baseURL,
168
+ apiKeyConfigured: config.llm.apiKey.trim().length > 0,
169
+ model: config.llm.model,
170
+ },
171
+ memoryGate: {
172
+ enabled: config.memoryGate.enabled,
173
+ windowSize: config.memoryGate.windowSize,
174
+ },
175
+ consolidation: {
176
+ enabled: config.consolidation.enabled,
177
+ schedule: config.consolidation.schedule,
178
+ },
179
+ };
180
+ }
181
+
182
+ export function resolveWorkspaceDir(
183
+ api: PluginAPI,
184
+ cwd: string = process.cwd()
185
+ ): WorkspaceResolution {
186
+ const configuredWorkspaceDir = getNonEmptyString(
187
+ readPluginConfigValue(api, "workspaceDir")
188
+ );
189
+
190
+ if (configuredWorkspaceDir) {
191
+ return {
192
+ workspaceDir: path.resolve(configuredWorkspaceDir),
193
+ source: "plugin config workspaceDir",
194
+ };
195
+ }
196
+
197
+ const normalizedCwd = getNonEmptyString(cwd);
198
+ if (normalizedCwd && !isFilesystemRoot(normalizedCwd)) {
199
+ return {
200
+ workspaceDir: path.resolve(normalizedCwd),
201
+ source: "process.cwd() fallback",
202
+ };
203
+ }
204
+
205
+ return {
206
+ source: "unresolved",
207
+ reason:
208
+ 'Missing plugin config "workspaceDir" and process.cwd() resolved to the filesystem root',
209
+ };
210
+ }
211
+
212
+ export function parseConfig(api: PluginAPI): PluginConfig {
213
+ return {
214
+ bufferSize: getPositiveInteger(
215
+ readPluginConfigValue(api, "bufferSize"),
216
+ DEFAULT_CONFIG.bufferSize
217
+ ),
218
+ logLevel: getLogLevel(readPluginConfigValue(api, "logLevel")),
219
+ llm: {
220
+ baseURL: getString(
221
+ readPluginConfigValue(api, "llm.baseURL"),
222
+ DEFAULT_CONFIG.llm.baseURL
223
+ ),
224
+ apiKey: getString(
225
+ readPluginConfigValue(api, "llm.apiKey"),
226
+ DEFAULT_CONFIG.llm.apiKey
227
+ ),
228
+ model: getString(
229
+ readPluginConfigValue(api, "llm.model"),
230
+ DEFAULT_CONFIG.llm.model
231
+ ),
232
+ },
233
+ memoryGate: {
234
+ enabled: getBoolean(
235
+ readPluginConfigValue(api, "memoryGate.enabled"),
236
+ DEFAULT_CONFIG.memoryGate.enabled
237
+ ),
238
+ windowSize: getPositiveInteger(
239
+ readPluginConfigValue(api, "memoryGate.windowSize"),
240
+ DEFAULT_CONFIG.memoryGate.windowSize
241
+ ),
242
+ },
243
+ consolidation: {
244
+ enabled: getBoolean(
245
+ readPluginConfigValue(api, "consolidation.enabled"),
246
+ DEFAULT_CONFIG.consolidation.enabled
247
+ ),
248
+ schedule: getString(
249
+ readPluginConfigValue(api, "consolidation.schedule"),
250
+ DEFAULT_CONFIG.consolidation.schedule
251
+ ),
252
+ },
253
+ };
254
+ }