@mingxy/cerebro 2.3.4 → 2.3.6

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/src/config.ts CHANGED
@@ -1,275 +1,277 @@
1
- import { readFileSync, appendFileSync, mkdirSync } from "node:fs";
2
- import { homedir } from "node:os";
3
- import { join } from "node:path";
4
-
5
- // ── Nested config interface ──────────────────────────────────────────
6
-
7
- export interface CerebroPluginConfig {
8
- connection: {
9
- apiUrl: string;
10
- apiKey: string;
11
- requestTimeoutMs: number;
12
- };
13
- content: {
14
- maxQueryLength: number;
15
- maxContentChars: number;
16
- maxContentLength: number;
17
- };
18
- injection: {
19
- recentCount: number;
20
- searchCount: number;
21
- recentTruncateChars: number;
22
- searchTruncateChars: number;
23
- recentTimeoutMs: number;
24
- searchTimeoutMs: number;
25
- profileTimeoutMs: number;
26
- };
27
- ingest: {
28
- autoCaptureThreshold: number;
29
- ingestMode: "smart" | "raw";
30
- };
31
- logging: {
32
- logEnabled: boolean;
33
- logLevel: "DEBUG" | "INFO" | "WARN" | "ERROR";
34
- logDir: string;
35
- };
36
- ui: {
37
- toastDelayMs: number;
38
- };
39
- web?: {
40
- enabled?: boolean;
41
- port?: number;
42
- };
43
- agentMemoryPolicy?: Record<string, "none" | "readonly" | "readwrite">;
44
- defaultPolicy?: "none" | "readonly" | "readwrite";
45
- autoUpdate?: boolean; // default false
46
- }
47
-
48
- // ── Defaults ─────────────────────────────────────────────────────────
49
-
50
- const DEFAULTS: CerebroPluginConfig = {
51
- connection: {
52
- apiUrl: "https://www.mengxy.cc",
53
- apiKey: "",
54
- requestTimeoutMs: 15000,
55
- },
56
- content: {
57
- maxQueryLength: 200,
58
- maxContentChars: 30000,
59
- maxContentLength: 3000,
60
- },
61
- injection: {
62
- recentCount: 5,
63
- searchCount: 10,
64
- recentTruncateChars: 0, // 0 = 不截断
65
- searchTruncateChars: 0, // 0 = 不截断
66
- recentTimeoutMs: 3000,
67
- searchTimeoutMs: 5000,
68
- profileTimeoutMs: 2000,
69
- },
70
- ingest: {
71
- autoCaptureThreshold: 5,
72
- ingestMode: "smart",
73
- },
74
- logging: {
75
- logEnabled: true,
76
- logLevel: "INFO",
77
- logDir: join(homedir(), ".config", "cerebro"),
78
- },
79
- ui: {
80
- toastDelayMs: 7000,
81
- },
82
- web: {
83
- enabled: true,
84
- },
85
- autoUpdate: false,
86
- };
87
-
88
- // ── Flat-to-nested migration ─────────────────────────────────────────
89
-
90
- /** Shape of legacy flat config (pre-nesting). */
91
- interface FlatConfig {
92
- apiUrl?: string;
93
- apiKey?: string;
94
- requestTimeoutMs?: number;
95
- maxQueryLength?: number;
96
- maxContentChars?: number;
97
- maxContentLength?: number;
98
- autoCaptureThreshold?: number;
99
- ingestMode?: "smart" | "raw";
100
- toastDelayMs?: number;
101
- logEnabled?: boolean;
102
- logLevel?: "DEBUG" | "INFO" | "WARN" | "ERROR";
103
- logDir?: string;
104
- // Nested fields that would indicate new format
105
- connection?: unknown;
106
- }
107
-
108
- function isFlatConfig(cfg: Record<string, unknown>): boolean {
109
- return "apiUrl" in cfg && !("connection" in cfg);
110
- }
111
-
112
- function migrateFlatToNested(flat: FlatConfig): CerebroPluginConfig {
113
- return {
114
- connection: {
115
- apiUrl: flat.apiUrl ?? DEFAULTS.connection.apiUrl,
116
- apiKey: flat.apiKey ?? DEFAULTS.connection.apiKey,
117
- requestTimeoutMs: flat.requestTimeoutMs ?? DEFAULTS.connection.requestTimeoutMs,
118
- },
119
- content: {
120
- maxQueryLength: flat.maxQueryLength ?? DEFAULTS.content.maxQueryLength,
121
- maxContentChars: flat.maxContentChars ?? DEFAULTS.content.maxContentChars,
122
- maxContentLength: flat.maxContentLength ?? DEFAULTS.content.maxContentLength,
123
- },
124
- injection: { ...DEFAULTS.injection },
125
- ingest: {
126
- autoCaptureThreshold: flat.autoCaptureThreshold ?? DEFAULTS.ingest.autoCaptureThreshold,
127
- ingestMode: flat.ingestMode ?? DEFAULTS.ingest.ingestMode,
128
- },
129
- logging: {
130
- logEnabled: flat.logEnabled ?? DEFAULTS.logging.logEnabled,
131
- logLevel: flat.logLevel ?? DEFAULTS.logging.logLevel,
132
- logDir: flat.logDir ?? DEFAULTS.logging.logDir,
133
- },
134
- ui: {
135
- toastDelayMs: flat.toastDelayMs ?? DEFAULTS.ui.toastDelayMs,
136
- },
137
- };
138
- }
139
-
140
- // ── Helpers ──────────────────────────────────────────────────────────
141
-
142
- type IngestMode = "smart" | "raw";
143
- const INGEST_MODES: ReadonlySet<string> = new Set<IngestMode>(["smart", "raw"]);
144
-
145
- function deepMerge(base: CerebroPluginConfig, overrides: Partial<CerebroPluginConfig>): CerebroPluginConfig {
146
- const result: CerebroPluginConfig = {
147
- connection: { ...base.connection, ...overrides.connection },
148
- content: { ...base.content, ...overrides.content },
149
- injection: { ...base.injection, ...overrides.injection },
150
- ingest: { ...base.ingest, ...overrides.ingest },
151
- logging: { ...base.logging, ...overrides.logging },
152
- ui: { ...base.ui, ...overrides.ui },
153
- };
154
- result.web = { ...base.web!, ...overrides.web };
155
- if (overrides.agentMemoryPolicy) result.agentMemoryPolicy = overrides.agentMemoryPolicy;
156
- if (overrides.defaultPolicy) result.defaultPolicy = overrides.defaultPolicy;
157
- if (overrides.autoUpdate !== undefined) result.autoUpdate = overrides.autoUpdate;
158
- return result;
159
- }
160
-
161
- // ── Load config ──────────────────────────────────────────────────────
162
-
163
- const LEVEL_MAP: Record<string, number> = { DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3 };
164
-
165
- function readConfiguredLogLevel(): number {
166
- try {
167
- const cfgPath = join(homedir(), ".config", "cerebro", "config.json");
168
- const raw = JSON.parse(readFileSync(cfgPath, "utf-8")) as Record<string, unknown>;
169
- const nested = (raw?.logging as Record<string, unknown>)?.logLevel as string | undefined;
170
- const flat = raw?.logLevel as string | undefined;
171
- const level = nested ?? flat ?? "INFO";
172
- return LEVEL_MAP[level] ?? LEVEL_MAP.INFO;
173
- } catch {
174
- return LEVEL_MAP.INFO;
175
- }
176
- }
177
-
178
- const CONFIGURED_MIN_LEVEL = readConfiguredLogLevel();
179
-
180
- /** File-only logger for config.ts (cannot import logger.ts due to circular dependency). */
181
- function configLog(message: string, fields?: Record<string, unknown>, level: string = "WARN"): void {
182
- const lvl = LEVEL_MAP[level] ?? 0;
183
- if (lvl < CONFIGURED_MIN_LEVEL) return;
184
- try {
185
- const logDir = join(homedir(), ".config", "cerebro", "logs");
186
- const logPath = join(logDir, "cerebro.log");
187
- const ts = new Date().toISOString().replace("T", " ").replace(/\.\d+Z$/, "");
188
- const parts = [`${level.padEnd(5)} ${ts} service=cerebro ${message}`];
189
- if (fields) {
190
- for (const [k, v] of Object.entries(fields)) {
191
- parts.push(`${k}=${typeof v === "string" ? v : JSON.stringify(v)}`);
192
- }
193
- }
194
- mkdirSync(logDir, { recursive: true });
195
- appendFileSync(logPath, parts.join(" ") + "\n");
196
- } catch (writeErr) {
197
- process.stderr.write(`[cerebro] configLog write failed: ${writeErr instanceof Error ? writeErr.message : String(writeErr)}\n`);
198
- }
199
- }
200
-
201
- export function loadPluginConfig(overrides?: Partial<CerebroPluginConfig>): CerebroPluginConfig {
202
- let config: CerebroPluginConfig = structuredClone(DEFAULTS);
203
-
204
- // Try loading from config file
205
- try {
206
- const cfgPath = join(homedir(), ".config", "cerebro", "config.json");
207
- const raw = JSON.parse(readFileSync(cfgPath, "utf-8")) as Record<string, unknown>;
208
-
209
- // Auto-migrate flat format
210
- const parsed: CerebroPluginConfig = isFlatConfig(raw) ? migrateFlatToNested(raw as FlatConfig) : raw as unknown as CerebroPluginConfig;
211
-
212
- // Merge nested groups with defaults for safety
213
- config = deepMerge(config, parsed);
214
- } catch (e) {
215
- configLog("config.json load failed, using defaults", { error: String(e) });
216
- }
217
-
218
- // Apply explicit overrides (from opencode.json)
219
- if (overrides) {
220
- config = deepMerge(config, overrides);
221
- }
222
-
223
- // Apply environment variable overrides last — env vars have highest priority
224
- if (process.env.OMEM_API_URL) config.connection.apiUrl = process.env.OMEM_API_URL;
225
- if (process.env.OMEM_API_KEY) config.connection.apiKey = process.env.OMEM_API_KEY;
226
- if (process.env.OMEM_REQUEST_TIMEOUT_MS) {
227
- config.connection.requestTimeoutMs = parseInt(process.env.OMEM_REQUEST_TIMEOUT_MS, 10) || DEFAULTS.connection.requestTimeoutMs;
228
- }
229
- if (process.env.OMEM_AUTO_CAPTURE_THRESHOLD) {
230
- config.ingest.autoCaptureThreshold = parseInt(process.env.OMEM_AUTO_CAPTURE_THRESHOLD, 10) || DEFAULTS.ingest.autoCaptureThreshold;
231
- }
232
- if (INGEST_MODES.has(process.env.OMEM_INGEST_MODE ?? "")) {
233
- config.ingest.ingestMode = process.env.OMEM_INGEST_MODE as IngestMode;
234
- }
235
-
236
- if (process.env.OMEM_WEB_ENABLED === "false" || process.env.OMEM_WEB_ENABLED === "0") {
237
- config.web = { ...config.web!, enabled: false };
238
- }
239
- if (process.env.OMEM_LOCAL_PORT) {
240
- config.web = { ...config.web!, port: parseInt(process.env.OMEM_LOCAL_PORT, 10) || DEFAULTS.web!.port };
241
- }
242
-
243
- // Expand ~ to home directory in logDir
244
- if (config.logging.logDir?.startsWith("~")) {
245
- config.logging.logDir = config.logging.logDir.replace(/^~/, homedir());
246
- }
247
-
248
- return config;
249
- }
250
-
251
- // ── Agent policy resolver ────────────────────────────────────────────
252
-
253
- export type AgentPolicy = "none" | "readonly" | "readwrite";
254
-
255
- export function resolveAgentPolicy(
256
- agentName: string,
257
- config: Partial<CerebroPluginConfig>,
258
- ): AgentPolicy {
259
- const policies = config.agentMemoryPolicy;
260
- if (policies) {
261
- const exact = policies[agentName];
262
- if (exact) return exact;
263
- const lower = agentName.toLowerCase();
264
- for (const [key, policy] of Object.entries(policies)) {
265
- if (lower.startsWith(key.toLowerCase()) || key.toLowerCase().startsWith(lower)) {
266
- return policy;
267
- }
268
- }
269
- }
270
- if (config.defaultPolicy) return config.defaultPolicy;
271
- configLog("resolveAgentPolicy: defaulting to readwrite", { agentName }, "DEBUG");
272
- return "readwrite";
273
- }
274
-
275
- export { DEFAULTS };
1
+ import { readFileSync, appendFileSync, mkdirSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+
5
+ // ── Nested config interface ──────────────────────────────────────────
6
+
7
+ export interface CerebroPluginConfig {
8
+ connection: {
9
+ apiUrl: string;
10
+ apiKey: string;
11
+ requestTimeoutMs: number;
12
+ };
13
+ content: {
14
+ maxQueryLength: number;
15
+ maxContentChars: number;
16
+ maxContentLength: number;
17
+ };
18
+ injection: {
19
+ recentCount: number;
20
+ searchCount: number;
21
+ globalCount: number;
22
+ recentTruncateChars: number;
23
+ searchTruncateChars: number;
24
+ recentTimeoutMs: number;
25
+ searchTimeoutMs: number;
26
+ profileTimeoutMs: number;
27
+ };
28
+ ingest: {
29
+ autoCaptureThreshold: number;
30
+ ingestMode: "smart" | "raw";
31
+ };
32
+ logging: {
33
+ logEnabled: boolean;
34
+ logLevel: "DEBUG" | "INFO" | "WARN" | "ERROR";
35
+ logDir: string;
36
+ };
37
+ ui: {
38
+ toastDelayMs: number;
39
+ };
40
+ web?: {
41
+ enabled?: boolean;
42
+ port?: number;
43
+ };
44
+ agentMemoryPolicy?: Record<string, "none" | "readonly" | "readwrite">;
45
+ defaultPolicy?: "none" | "readonly" | "readwrite";
46
+ autoUpdate?: boolean; // default false
47
+ }
48
+
49
+ // ── Defaults ─────────────────────────────────────────────────────────
50
+
51
+ const DEFAULTS: CerebroPluginConfig = {
52
+ connection: {
53
+ apiUrl: "https://www.mengxy.cc",
54
+ apiKey: "",
55
+ requestTimeoutMs: 15000,
56
+ },
57
+ content: {
58
+ maxQueryLength: 200,
59
+ maxContentChars: 30000,
60
+ maxContentLength: 3000,
61
+ },
62
+ injection: {
63
+ recentCount: 5,
64
+ searchCount: 10,
65
+ globalCount: 3,
66
+ recentTruncateChars: 0, // 0 = 不截断
67
+ searchTruncateChars: 0, // 0 = 不截断
68
+ recentTimeoutMs: 3000,
69
+ searchTimeoutMs: 5000,
70
+ profileTimeoutMs: 2000,
71
+ },
72
+ ingest: {
73
+ autoCaptureThreshold: 5,
74
+ ingestMode: "smart",
75
+ },
76
+ logging: {
77
+ logEnabled: true,
78
+ logLevel: "INFO",
79
+ logDir: join(homedir(), ".config", "cerebro"),
80
+ },
81
+ ui: {
82
+ toastDelayMs: 7000,
83
+ },
84
+ web: {
85
+ enabled: true,
86
+ },
87
+ autoUpdate: false,
88
+ };
89
+
90
+ // ── Flat-to-nested migration ─────────────────────────────────────────
91
+
92
+ /** Shape of legacy flat config (pre-nesting). */
93
+ interface FlatConfig {
94
+ apiUrl?: string;
95
+ apiKey?: string;
96
+ requestTimeoutMs?: number;
97
+ maxQueryLength?: number;
98
+ maxContentChars?: number;
99
+ maxContentLength?: number;
100
+ autoCaptureThreshold?: number;
101
+ ingestMode?: "smart" | "raw";
102
+ toastDelayMs?: number;
103
+ logEnabled?: boolean;
104
+ logLevel?: "DEBUG" | "INFO" | "WARN" | "ERROR";
105
+ logDir?: string;
106
+ // Nested fields that would indicate new format
107
+ connection?: unknown;
108
+ }
109
+
110
+ function isFlatConfig(cfg: Record<string, unknown>): boolean {
111
+ return "apiUrl" in cfg && !("connection" in cfg);
112
+ }
113
+
114
+ function migrateFlatToNested(flat: FlatConfig): CerebroPluginConfig {
115
+ return {
116
+ connection: {
117
+ apiUrl: flat.apiUrl ?? DEFAULTS.connection.apiUrl,
118
+ apiKey: flat.apiKey ?? DEFAULTS.connection.apiKey,
119
+ requestTimeoutMs: flat.requestTimeoutMs ?? DEFAULTS.connection.requestTimeoutMs,
120
+ },
121
+ content: {
122
+ maxQueryLength: flat.maxQueryLength ?? DEFAULTS.content.maxQueryLength,
123
+ maxContentChars: flat.maxContentChars ?? DEFAULTS.content.maxContentChars,
124
+ maxContentLength: flat.maxContentLength ?? DEFAULTS.content.maxContentLength,
125
+ },
126
+ injection: { ...DEFAULTS.injection },
127
+ ingest: {
128
+ autoCaptureThreshold: flat.autoCaptureThreshold ?? DEFAULTS.ingest.autoCaptureThreshold,
129
+ ingestMode: flat.ingestMode ?? DEFAULTS.ingest.ingestMode,
130
+ },
131
+ logging: {
132
+ logEnabled: flat.logEnabled ?? DEFAULTS.logging.logEnabled,
133
+ logLevel: flat.logLevel ?? DEFAULTS.logging.logLevel,
134
+ logDir: flat.logDir ?? DEFAULTS.logging.logDir,
135
+ },
136
+ ui: {
137
+ toastDelayMs: flat.toastDelayMs ?? DEFAULTS.ui.toastDelayMs,
138
+ },
139
+ };
140
+ }
141
+
142
+ // ── Helpers ──────────────────────────────────────────────────────────
143
+
144
+ type IngestMode = "smart" | "raw";
145
+ const INGEST_MODES: ReadonlySet<string> = new Set<IngestMode>(["smart", "raw"]);
146
+
147
+ function deepMerge(base: CerebroPluginConfig, overrides: Partial<CerebroPluginConfig>): CerebroPluginConfig {
148
+ const result: CerebroPluginConfig = {
149
+ connection: { ...base.connection, ...overrides.connection },
150
+ content: { ...base.content, ...overrides.content },
151
+ injection: { ...base.injection, ...overrides.injection },
152
+ ingest: { ...base.ingest, ...overrides.ingest },
153
+ logging: { ...base.logging, ...overrides.logging },
154
+ ui: { ...base.ui, ...overrides.ui },
155
+ };
156
+ result.web = { ...base.web!, ...overrides.web };
157
+ if (overrides.agentMemoryPolicy) result.agentMemoryPolicy = overrides.agentMemoryPolicy;
158
+ if (overrides.defaultPolicy) result.defaultPolicy = overrides.defaultPolicy;
159
+ if (overrides.autoUpdate !== undefined) result.autoUpdate = overrides.autoUpdate;
160
+ return result;
161
+ }
162
+
163
+ // ── Load config ──────────────────────────────────────────────────────
164
+
165
+ const LEVEL_MAP: Record<string, number> = { DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3 };
166
+
167
+ function readConfiguredLogLevel(): number {
168
+ try {
169
+ const cfgPath = join(homedir(), ".config", "cerebro", "config.json");
170
+ const raw = JSON.parse(readFileSync(cfgPath, "utf-8")) as Record<string, unknown>;
171
+ const nested = (raw?.logging as Record<string, unknown>)?.logLevel as string | undefined;
172
+ const flat = raw?.logLevel as string | undefined;
173
+ const level = nested ?? flat ?? "INFO";
174
+ return LEVEL_MAP[level] ?? LEVEL_MAP.INFO;
175
+ } catch {
176
+ return LEVEL_MAP.INFO;
177
+ }
178
+ }
179
+
180
+ const CONFIGURED_MIN_LEVEL = readConfiguredLogLevel();
181
+
182
+ /** File-only logger for config.ts (cannot import logger.ts due to circular dependency). */
183
+ function configLog(message: string, fields?: Record<string, unknown>, level: string = "WARN"): void {
184
+ const lvl = LEVEL_MAP[level] ?? 0;
185
+ if (lvl < CONFIGURED_MIN_LEVEL) return;
186
+ try {
187
+ const logDir = join(homedir(), ".config", "cerebro", "logs");
188
+ const logPath = join(logDir, "cerebro.log");
189
+ const ts = new Date().toISOString().replace("T", " ").replace(/\.\d+Z$/, "");
190
+ const parts = [`${level.padEnd(5)} ${ts} service=cerebro ${message}`];
191
+ if (fields) {
192
+ for (const [k, v] of Object.entries(fields)) {
193
+ parts.push(`${k}=${typeof v === "string" ? v : JSON.stringify(v)}`);
194
+ }
195
+ }
196
+ mkdirSync(logDir, { recursive: true });
197
+ appendFileSync(logPath, parts.join(" ") + "\n");
198
+ } catch (writeErr) {
199
+ process.stderr.write(`[cerebro] configLog write failed: ${writeErr instanceof Error ? writeErr.message : String(writeErr)}\n`);
200
+ }
201
+ }
202
+
203
+ export function loadPluginConfig(overrides?: Partial<CerebroPluginConfig>): CerebroPluginConfig {
204
+ let config: CerebroPluginConfig = structuredClone(DEFAULTS);
205
+
206
+ // Try loading from config file
207
+ try {
208
+ const cfgPath = join(homedir(), ".config", "cerebro", "config.json");
209
+ const raw = JSON.parse(readFileSync(cfgPath, "utf-8")) as Record<string, unknown>;
210
+
211
+ // Auto-migrate flat format
212
+ const parsed: CerebroPluginConfig = isFlatConfig(raw) ? migrateFlatToNested(raw as FlatConfig) : raw as unknown as CerebroPluginConfig;
213
+
214
+ // Merge nested groups with defaults for safety
215
+ config = deepMerge(config, parsed);
216
+ } catch (e) {
217
+ configLog("config.json load failed, using defaults", { error: String(e) });
218
+ }
219
+
220
+ // Apply explicit overrides (from opencode.json)
221
+ if (overrides) {
222
+ config = deepMerge(config, overrides);
223
+ }
224
+
225
+ // Apply environment variable overrides last — env vars have highest priority
226
+ if (process.env.OMEM_API_URL) config.connection.apiUrl = process.env.OMEM_API_URL;
227
+ if (process.env.OMEM_API_KEY) config.connection.apiKey = process.env.OMEM_API_KEY;
228
+ if (process.env.OMEM_REQUEST_TIMEOUT_MS) {
229
+ config.connection.requestTimeoutMs = parseInt(process.env.OMEM_REQUEST_TIMEOUT_MS, 10) || DEFAULTS.connection.requestTimeoutMs;
230
+ }
231
+ if (process.env.OMEM_AUTO_CAPTURE_THRESHOLD) {
232
+ config.ingest.autoCaptureThreshold = parseInt(process.env.OMEM_AUTO_CAPTURE_THRESHOLD, 10) || DEFAULTS.ingest.autoCaptureThreshold;
233
+ }
234
+ if (INGEST_MODES.has(process.env.OMEM_INGEST_MODE ?? "")) {
235
+ config.ingest.ingestMode = process.env.OMEM_INGEST_MODE as IngestMode;
236
+ }
237
+
238
+ if (process.env.OMEM_WEB_ENABLED === "false" || process.env.OMEM_WEB_ENABLED === "0") {
239
+ config.web = { ...config.web!, enabled: false };
240
+ }
241
+ if (process.env.OMEM_LOCAL_PORT) {
242
+ config.web = { ...config.web!, port: parseInt(process.env.OMEM_LOCAL_PORT, 10) || DEFAULTS.web!.port };
243
+ }
244
+
245
+ // Expand ~ to home directory in logDir
246
+ if (config.logging.logDir?.startsWith("~")) {
247
+ config.logging.logDir = config.logging.logDir.replace(/^~/, homedir());
248
+ }
249
+
250
+ return config;
251
+ }
252
+
253
+ // ── Agent policy resolver ────────────────────────────────────────────
254
+
255
+ export type AgentPolicy = "none" | "readonly" | "readwrite";
256
+
257
+ export function resolveAgentPolicy(
258
+ agentName: string,
259
+ config: Partial<CerebroPluginConfig>,
260
+ ): AgentPolicy {
261
+ const policies = config.agentMemoryPolicy;
262
+ if (policies) {
263
+ const exact = policies[agentName];
264
+ if (exact) return exact;
265
+ const lower = agentName.toLowerCase();
266
+ for (const [key, policy] of Object.entries(policies)) {
267
+ if (lower.startsWith(key.toLowerCase()) || key.toLowerCase().startsWith(lower)) {
268
+ return policy;
269
+ }
270
+ }
271
+ }
272
+ if (config.defaultPolicy) return config.defaultPolicy;
273
+ configLog("resolveAgentPolicy: defaulting to readwrite", { agentName }, "DEBUG");
274
+ return "readwrite";
275
+ }
276
+
277
+ export { DEFAULTS };