@sonzai-labs/openclaw-context 1.0.3

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/dist/index.js ADDED
@@ -0,0 +1,638 @@
1
+ import { Sonzai } from '@sonzai-labs/agents';
2
+ import 'readline';
3
+ import * as fs from 'fs';
4
+ import * as path from 'path';
5
+
6
+ // src/plugin.ts
7
+
8
+ // src/config.ts
9
+ var DEFAULTS = {
10
+ baseUrl: "https://api.sonz.ai",
11
+ agentName: "openclaw-agent",
12
+ defaultUserId: "owner",
13
+ contextTokenBudget: 2e3
14
+ };
15
+ function resolveConfig(raw) {
16
+ const apiKey = raw?.apiKey || env("SONZAI_API_KEY") || env("SONZAI_OPENCLAW_API_KEY");
17
+ if (!apiKey) {
18
+ throw new Error(
19
+ "[@sonzai-labs/openclaw-context] Missing API key. Set apiKey in plugin config or SONZAI_API_KEY environment variable."
20
+ );
21
+ }
22
+ return {
23
+ apiKey,
24
+ agentId: raw?.agentId || env("SONZAI_AGENT_ID") || void 0,
25
+ baseUrl: raw?.baseUrl || env("SONZAI_BASE_URL") || DEFAULTS.baseUrl,
26
+ agentName: raw?.agentName || env("SONZAI_AGENT_NAME") || DEFAULTS.agentName,
27
+ defaultUserId: raw?.defaultUserId || DEFAULTS.defaultUserId,
28
+ contextTokenBudget: raw?.contextTokenBudget ?? DEFAULTS.contextTokenBudget,
29
+ disable: raw?.disable ?? {},
30
+ extractionProvider: raw?.extractionProvider || env("SONZAI_EXTRACTION_PROVIDER") || void 0,
31
+ extractionModel: raw?.extractionModel || env("SONZAI_EXTRACTION_MODEL") || void 0
32
+ };
33
+ }
34
+ function env(key) {
35
+ try {
36
+ return typeof process !== "undefined" ? process.env[key] : void 0;
37
+ } catch {
38
+ return void 0;
39
+ }
40
+ }
41
+
42
+ // src/cache.ts
43
+ var SessionCache = class {
44
+ constructor(ttlMs) {
45
+ this.ttlMs = ttlMs;
46
+ }
47
+ store = /* @__PURE__ */ new Map();
48
+ get(key) {
49
+ const entry = this.store.get(key);
50
+ if (!entry) return void 0;
51
+ if (Date.now() > entry.expiresAt) {
52
+ this.store.delete(key);
53
+ return void 0;
54
+ }
55
+ return entry.value;
56
+ }
57
+ set(key, value) {
58
+ this.store.set(key, {
59
+ value,
60
+ expiresAt: Date.now() + this.ttlMs
61
+ });
62
+ }
63
+ delete(key) {
64
+ this.store.delete(key);
65
+ }
66
+ clear() {
67
+ this.store.clear();
68
+ }
69
+ };
70
+
71
+ // src/context-builder.ts
72
+ var SECTION_ORDER = [
73
+ "habits",
74
+ "interests",
75
+ "goals",
76
+ "relationships",
77
+ "mood",
78
+ "memories",
79
+ "personality"
80
+ ];
81
+ var formatters = {
82
+ personality: formatPersonality,
83
+ mood: formatMood,
84
+ relationships: formatRelationships,
85
+ memories: formatMemories,
86
+ goals: formatGoals,
87
+ interests: formatInterests,
88
+ habits: formatHabits
89
+ };
90
+ function buildSystemPromptAddition(sources, tokenBudget) {
91
+ const sections = [];
92
+ for (const key of [...SECTION_ORDER].reverse()) {
93
+ const data = sources[key];
94
+ if (!data) continue;
95
+ const text = formatters[key](data);
96
+ if (text) sections.push({ key, text });
97
+ }
98
+ if (sections.length === 0) return "";
99
+ let result = "<sonzai-context>\n" + sections.map((s) => s.text).join("\n\n") + "\n</sonzai-context>";
100
+ while (estimateTokens(result) > tokenBudget && sections.length > 1) {
101
+ const dropKey = SECTION_ORDER.find(
102
+ (k) => sections.some((s) => s.key === k)
103
+ );
104
+ if (!dropKey) break;
105
+ const idx = sections.findIndex((s) => s.key === dropKey);
106
+ if (idx === -1) break;
107
+ sections.splice(idx, 1);
108
+ result = "<sonzai-context>\n" + sections.map((s) => s.text).join("\n\n") + "\n</sonzai-context>";
109
+ }
110
+ if (estimateTokens(result) > tokenBudget) {
111
+ const maxChars = tokenBudget * 4;
112
+ result = result.slice(0, maxChars) + "\n[...truncated]\n</sonzai-context>";
113
+ }
114
+ return result;
115
+ }
116
+ function estimateTokens(text) {
117
+ return Math.ceil(text.length / 4);
118
+ }
119
+ function formatPersonality(data) {
120
+ const resp = data;
121
+ const p = resp?.profile;
122
+ if (!p) return null;
123
+ const lines = ["## Personality"];
124
+ lines.push(`Name: ${p.name}`);
125
+ if (p.bio) lines.push(`Bio: ${p.bio}`);
126
+ if (p.primary_traits?.length) lines.push(`Traits: ${p.primary_traits.join(", ")}`);
127
+ if (p.speech_patterns?.length) lines.push(`Speech patterns: ${p.speech_patterns.join(", ")}`);
128
+ if (p.personality_prompt) lines.push(`Character: ${p.personality_prompt}`);
129
+ if (p.big5) {
130
+ const b5 = p.big5;
131
+ const traits = [
132
+ `O:${b5.openness?.score ?? "?"}`,
133
+ `C:${b5.conscientiousness?.score ?? "?"}`,
134
+ `E:${b5.extraversion?.score ?? "?"}`,
135
+ `A:${b5.agreeableness?.score ?? "?"}`,
136
+ `N:${b5.neuroticism?.score ?? "?"}`
137
+ ];
138
+ lines.push(`Big5: ${traits.join(" ")}`);
139
+ }
140
+ if (p.preferences) {
141
+ const pref = p.preferences;
142
+ lines.push(
143
+ `Style: pace=${pref.pace}, formality=${pref.formality}, humor=${pref.humor_style}, expression=${pref.emotional_expression}`
144
+ );
145
+ }
146
+ const evo = resp?.evolution;
147
+ if (evo?.length) {
148
+ lines.push("Recent shifts:");
149
+ for (const d of evo.slice(0, 3)) {
150
+ lines.push(`- ${d.change} (${d.reason})`);
151
+ }
152
+ }
153
+ return lines.join("\n");
154
+ }
155
+ function formatMood(data) {
156
+ const mood = data;
157
+ if (!mood || Object.keys(mood).length === 0) return null;
158
+ const lines = ["## Current Mood"];
159
+ for (const [key, value] of Object.entries(mood)) {
160
+ if (key.startsWith("_") || value === null || value === void 0) continue;
161
+ if (typeof value === "object") {
162
+ lines.push(`${key}: ${JSON.stringify(value)}`);
163
+ } else {
164
+ lines.push(`${key}: ${value}`);
165
+ }
166
+ }
167
+ return lines.length > 1 ? lines.join("\n") : null;
168
+ }
169
+ function formatRelationships(data) {
170
+ const rels = data;
171
+ if (!rels || Object.keys(rels).length === 0) return null;
172
+ const lines = ["## Relationship"];
173
+ for (const [key, value] of Object.entries(rels)) {
174
+ if (key.startsWith("_") || value === null || value === void 0) continue;
175
+ if (typeof value === "object") {
176
+ lines.push(`${key}: ${JSON.stringify(value)}`);
177
+ } else {
178
+ lines.push(`${key}: ${value}`);
179
+ }
180
+ }
181
+ return lines.length > 1 ? lines.join("\n") : null;
182
+ }
183
+ function formatMemories(data) {
184
+ const resp = data;
185
+ if (!resp?.results?.length) return null;
186
+ const lines = ["## Relevant Memories"];
187
+ for (const mem of resp.results.slice(0, 10)) {
188
+ const score = mem.score ? ` (score: ${mem.score.toFixed(2)})` : "";
189
+ lines.push(`- ${mem.content}${score}`);
190
+ }
191
+ return lines.join("\n");
192
+ }
193
+ function formatGoals(data) {
194
+ const resp = data;
195
+ if (!resp?.goals?.length) return null;
196
+ const lines = ["## Goals"];
197
+ for (const g of resp.goals.filter((g2) => g2.status === "active").slice(0, 5)) {
198
+ lines.push(`- ${g.title}: ${g.description} [${g.type}, priority=${g.priority}]`);
199
+ }
200
+ return lines.length > 1 ? lines.join("\n") : null;
201
+ }
202
+ function formatInterests(data) {
203
+ const interests = data;
204
+ if (!interests || Object.keys(interests).length === 0) return null;
205
+ const lines = ["## Interests"];
206
+ for (const [key, value] of Object.entries(interests)) {
207
+ if (key.startsWith("_") || value === null || value === void 0) continue;
208
+ if (typeof value === "object") {
209
+ lines.push(`${key}: ${JSON.stringify(value)}`);
210
+ } else {
211
+ lines.push(`${key}: ${value}`);
212
+ }
213
+ }
214
+ return lines.length > 1 ? lines.join("\n") : null;
215
+ }
216
+ function formatHabits(data) {
217
+ const habits = data;
218
+ if (!habits || Object.keys(habits).length === 0) return null;
219
+ const lines = ["## Habits"];
220
+ for (const [key, value] of Object.entries(habits)) {
221
+ if (key.startsWith("_") || value === null || value === void 0) continue;
222
+ if (typeof value === "object") {
223
+ lines.push(`${key}: ${JSON.stringify(value)}`);
224
+ } else {
225
+ lines.push(`${key}: ${value}`);
226
+ }
227
+ }
228
+ return lines.length > 1 ? lines.join("\n") : null;
229
+ }
230
+ function buildSystemPromptFromContext(ctx, tokenBudget, disable = {}) {
231
+ const sections = [];
232
+ if (!disable.personality && ctx.personality_prompt) {
233
+ const lines = ["## Personality"];
234
+ lines.push(`Character: ${ctx.personality_prompt}`);
235
+ if (ctx.primary_traits?.length) lines.push(`Traits: ${ctx.primary_traits.join(", ")}`);
236
+ if (ctx.speech_patterns?.length) lines.push(`Speech patterns: ${ctx.speech_patterns.join(", ")}`);
237
+ if (ctx.big5) {
238
+ const b5 = ctx.big5;
239
+ const traits = ["openness", "conscientiousness", "extraversion", "agreeableness", "neuroticism"].map((t) => `${t[0].toUpperCase()}:${b5[t]?.score ?? "?"}`).join(" ");
240
+ lines.push(`Big5: ${traits}`);
241
+ }
242
+ sections.push({ key: "personality", text: lines.join("\n"), priority: 7 });
243
+ }
244
+ if (!disable.memory) {
245
+ const facts = ctx.loaded_facts;
246
+ if (facts?.length) {
247
+ const lines = ["## Relevant Memories"];
248
+ for (const f of facts.slice(0, 10)) {
249
+ const text = f.atomic_text || f.content || JSON.stringify(f);
250
+ lines.push(`- ${text}`);
251
+ }
252
+ sections.push({ key: "memories", text: lines.join("\n"), priority: 6 });
253
+ }
254
+ }
255
+ if (!disable.mood && ctx.current_mood) {
256
+ const mood = ctx.current_mood;
257
+ const lines = ["## Current Mood"];
258
+ for (const [key, value] of Object.entries(mood)) {
259
+ if (key.startsWith("_") || value === null || value === void 0) continue;
260
+ lines.push(`${key}: ${typeof value === "object" ? JSON.stringify(value) : value}`);
261
+ }
262
+ if (lines.length > 1) sections.push({ key: "mood", text: lines.join("\n"), priority: 5 });
263
+ }
264
+ if (!disable.relationships && ctx.relationship_narrative) {
265
+ const lines = ["## Relationship"];
266
+ lines.push(ctx.relationship_narrative);
267
+ if (ctx.love_from_agent !== void 0) lines.push(`Love (agent->user): ${ctx.love_from_agent}`);
268
+ if (ctx.love_from_user !== void 0) lines.push(`Love (user->agent): ${ctx.love_from_user}`);
269
+ if (ctx.relationship_status) lines.push(`Status: ${ctx.relationship_status}`);
270
+ sections.push({ key: "relationships", text: lines.join("\n"), priority: 4 });
271
+ }
272
+ if (!disable.goals) {
273
+ const goals = ctx.active_goals;
274
+ if (goals?.length) {
275
+ const lines = ["## Goals"];
276
+ for (const g of goals.slice(0, 5)) {
277
+ lines.push(`- ${g.title}: ${g.description} [${g.type}]`);
278
+ }
279
+ sections.push({ key: "goals", text: lines.join("\n"), priority: 3 });
280
+ }
281
+ }
282
+ if (!disable.interests && ctx.true_interests?.length) {
283
+ sections.push({ key: "interests", text: `## Interests
284
+ ${ctx.true_interests.join(", ")}`, priority: 2 });
285
+ }
286
+ if (!disable.habits) {
287
+ const ctxHabits = ctx.habits;
288
+ if (ctxHabits?.length) {
289
+ const lines = ["## Habits"];
290
+ for (const h of ctxHabits.slice(0, 5)) {
291
+ lines.push(`- ${h.name}: ${h.description || h.category || ""}`);
292
+ }
293
+ sections.push({ key: "habits", text: lines.join("\n"), priority: 1 });
294
+ }
295
+ }
296
+ if (sections.length === 0) return "";
297
+ sections.sort((a, b) => b.priority - a.priority);
298
+ let result = "<sonzai-context>\n" + sections.map((s) => s.text).join("\n\n") + "\n</sonzai-context>";
299
+ while (estimateTokens(result) > tokenBudget && sections.length > 1) {
300
+ sections.pop();
301
+ result = "<sonzai-context>\n" + sections.map((s) => s.text).join("\n\n") + "\n</sonzai-context>";
302
+ }
303
+ if (estimateTokens(result) > tokenBudget) {
304
+ const maxChars = tokenBudget * 4;
305
+ result = result.slice(0, maxChars) + "\n[...truncated]\n</sonzai-context>";
306
+ }
307
+ return result;
308
+ }
309
+
310
+ // src/session-key.ts
311
+ function parseSessionKey(sessionId, defaultUserId) {
312
+ const parts = sessionId.split(":");
313
+ if (parts[0] === "agent" && parts.length >= 3) {
314
+ const agentId = parts[1] ?? null;
315
+ const directIdx = parts.indexOf("direct");
316
+ if (directIdx !== -1 && directIdx + 1 < parts.length) {
317
+ const channel = directIdx >= 3 ? parts[2] : null;
318
+ const peerId = parts.slice(directIdx + 1).join(":");
319
+ return {
320
+ agentId,
321
+ userId: peerId,
322
+ channel,
323
+ sessionType: "dm"
324
+ };
325
+ }
326
+ const groupIdx = parts.indexOf("group");
327
+ if (groupIdx !== -1 && groupIdx + 1 < parts.length) {
328
+ const channel = groupIdx >= 3 ? parts[2] : null;
329
+ const groupId = parts.slice(groupIdx + 1).join(":");
330
+ return {
331
+ agentId,
332
+ userId: groupId,
333
+ channel,
334
+ sessionType: "group"
335
+ };
336
+ }
337
+ return {
338
+ agentId,
339
+ userId: defaultUserId,
340
+ channel: null,
341
+ sessionType: "cli"
342
+ };
343
+ }
344
+ if (parts[0] === "cron") {
345
+ return {
346
+ agentId: null,
347
+ userId: defaultUserId,
348
+ channel: null,
349
+ sessionType: "cron"
350
+ };
351
+ }
352
+ if (parts[0] === "hook") {
353
+ return {
354
+ agentId: null,
355
+ userId: defaultUserId,
356
+ channel: null,
357
+ sessionType: "webhook"
358
+ };
359
+ }
360
+ return {
361
+ agentId: null,
362
+ userId: defaultUserId,
363
+ channel: null,
364
+ sessionType: "unknown"
365
+ };
366
+ }
367
+
368
+ // src/engine.ts
369
+ var AGENT_CACHE_TTL = 30 * 60 * 1e3;
370
+ var SonzaiContextEngine = class {
371
+ constructor(client, config) {
372
+ this.client = client;
373
+ this.config = config;
374
+ }
375
+ info = {
376
+ id: "sonzai",
377
+ name: "Sonzai Mind Layer",
378
+ ownsCompaction: true
379
+ };
380
+ sessions = /* @__PURE__ */ new Map();
381
+ agentCache = new SessionCache(AGENT_CACHE_TTL);
382
+ // -------------------------------------------------------------------------
383
+ // bootstrap — initialize session, optionally auto-provision agent
384
+ // -------------------------------------------------------------------------
385
+ async bootstrap({ sessionId }) {
386
+ try {
387
+ const parsed = parseSessionKey(sessionId, this.config.defaultUserId);
388
+ const agentId = await this.resolveAgentId(parsed.agentId);
389
+ await this.client.agents.sessions.start(agentId, {
390
+ userId: parsed.userId,
391
+ sessionId
392
+ });
393
+ this.sessions.set(sessionId, {
394
+ agentId,
395
+ userId: parsed.userId,
396
+ sonzaiSessionId: sessionId,
397
+ startedAt: Date.now(),
398
+ turnCount: 0,
399
+ lastProcessedIndex: 0,
400
+ lastMessages: []
401
+ });
402
+ } catch (err) {
403
+ console.warn("[@sonzai-labs/openclaw-context] bootstrap failed:", err);
404
+ }
405
+ }
406
+ // -------------------------------------------------------------------------
407
+ // ingest — no-op; processing happens in afterTurn with full conversation
408
+ // -------------------------------------------------------------------------
409
+ async ingest(_args) {
410
+ return { ingested: true };
411
+ }
412
+ // -------------------------------------------------------------------------
413
+ // assemble — fetch enriched context and inject as systemPromptAddition
414
+ // -------------------------------------------------------------------------
415
+ async assemble({
416
+ sessionId,
417
+ messages,
418
+ tokenBudget,
419
+ origin
420
+ }) {
421
+ const session = this.sessions.get(sessionId);
422
+ if (!session) {
423
+ return { messages, estimatedTokens: 0 };
424
+ }
425
+ session.lastMessages = messages;
426
+ try {
427
+ const { agentId, userId } = session;
428
+ const lastUserMsg = findLastUserMessage(messages);
429
+ const budget = Math.min(tokenBudget, this.config.contextTokenBudget);
430
+ const effectiveUserId = origin?.from && origin?.provider ? `${origin.provider}:${origin.from}` : userId;
431
+ const context = await this.client.agents.getContext(agentId, {
432
+ userId: effectiveUserId,
433
+ sessionId: session.sonzaiSessionId,
434
+ query: lastUserMsg
435
+ });
436
+ const systemPromptAddition = buildSystemPromptFromContext(
437
+ context,
438
+ budget,
439
+ this.config.disable
440
+ );
441
+ return {
442
+ messages,
443
+ estimatedTokens: estimateTokens(systemPromptAddition),
444
+ systemPromptAddition: systemPromptAddition || void 0
445
+ };
446
+ } catch (err) {
447
+ console.warn("[@sonzai-labs/openclaw-context] assemble failed:", err);
448
+ return { messages, estimatedTokens: 0 };
449
+ }
450
+ }
451
+ // -------------------------------------------------------------------------
452
+ // compact — trigger Sonzai's consolidation pipeline
453
+ // -------------------------------------------------------------------------
454
+ async compact({ sessionId }) {
455
+ const session = this.sessions.get(sessionId);
456
+ if (!session) {
457
+ return { ok: false, compacted: false };
458
+ }
459
+ try {
460
+ await this.client.agents.consolidate(session.agentId);
461
+ return { ok: true, compacted: true };
462
+ } catch (err) {
463
+ console.warn("[@sonzai-labs/openclaw-context] compact failed:", err);
464
+ return { ok: false, compacted: false };
465
+ }
466
+ }
467
+ // -------------------------------------------------------------------------
468
+ // afterTurn — send new messages for fact extraction
469
+ // -------------------------------------------------------------------------
470
+ async afterTurn({ sessionId }) {
471
+ const session = this.sessions.get(sessionId);
472
+ if (!session) return;
473
+ try {
474
+ session.turnCount++;
475
+ const newMessages = session.lastMessages.slice(session.lastProcessedIndex);
476
+ session.lastProcessedIndex = session.lastMessages.length;
477
+ if (newMessages.length === 0) return;
478
+ await this.client.agents.process(session.agentId, {
479
+ userId: session.userId,
480
+ sessionId: session.sonzaiSessionId,
481
+ messages: newMessages.map((m) => ({
482
+ role: m.role,
483
+ content: m.content
484
+ })),
485
+ provider: this.config.extractionProvider,
486
+ model: this.config.extractionModel
487
+ });
488
+ } catch (err) {
489
+ console.warn("[@sonzai-labs/openclaw-context] afterTurn failed:", err);
490
+ }
491
+ }
492
+ // -------------------------------------------------------------------------
493
+ // dispose — end all active sessions and clear caches
494
+ // -------------------------------------------------------------------------
495
+ async dispose() {
496
+ const endPromises = [...this.sessions.entries()].map(
497
+ async ([_sessionId, session]) => {
498
+ try {
499
+ await this.client.agents.sessions.end(session.agentId, {
500
+ userId: session.userId,
501
+ sessionId: session.sonzaiSessionId,
502
+ totalMessages: session.turnCount,
503
+ durationSeconds: Math.floor(
504
+ (Date.now() - session.startedAt) / 1e3
505
+ )
506
+ });
507
+ } catch {
508
+ }
509
+ }
510
+ );
511
+ await Promise.allSettled(endPromises);
512
+ this.sessions.clear();
513
+ this.agentCache.clear();
514
+ }
515
+ // -------------------------------------------------------------------------
516
+ // Private helpers
517
+ // -------------------------------------------------------------------------
518
+ /**
519
+ * Resolve the Sonzai agent ID: use config if provided, otherwise
520
+ * auto-provision via the idempotent create endpoint.
521
+ *
522
+ * Idempotency guarantee: the backend derives a deterministic UUID v5 from
523
+ * `SHA1(namespace, tenantID + "/" + lowercase(name))`. Same API key (tenant)
524
+ * + same name → same UUID on every call, even across restarts or pod
525
+ * replacements.
526
+ *
527
+ * The name used is always `config.agentName` (default: "openclaw-agent").
528
+ * This is stable because it comes from the plugin config / env var, not
529
+ * from any runtime state like pod IDs or session UUIDs.
530
+ *
531
+ * If a B2B client needs multiple distinct agents under one API key, they
532
+ * set a unique `agentName` per OpenClaw instance in their config.
533
+ */
534
+ async resolveAgentId(_sessionAgentId) {
535
+ if (this.config.agentId) {
536
+ return this.config.agentId;
537
+ }
538
+ const name = this.config.agentName;
539
+ const cached = this.agentCache.get(name);
540
+ if (cached) return cached;
541
+ const agent = await this.client.agents.create({ name });
542
+ this.agentCache.set(name, agent.agent_id);
543
+ return agent.agent_id;
544
+ }
545
+ };
546
+ function findLastUserMessage(messages) {
547
+ for (let i = messages.length - 1; i >= 0; i--) {
548
+ if (messages[i].role === "user") {
549
+ return messages[i].content;
550
+ }
551
+ }
552
+ return void 0;
553
+ }
554
+
555
+ // src/plugin.ts
556
+ function register(api) {
557
+ api.registerContextEngine("sonzai", () => {
558
+ const config = resolveConfig();
559
+ const client = new Sonzai({
560
+ apiKey: config.apiKey,
561
+ baseUrl: config.baseUrl
562
+ });
563
+ return new SonzaiContextEngine(client, config);
564
+ });
565
+ }
566
+ async function setup(options) {
567
+ const baseUrl = options.baseUrl || "https://api.sonz.ai";
568
+ const agentName = options.agentName || "openclaw-agent";
569
+ const writeConfig = options.writeConfig ?? true;
570
+ const configPath = options.configPath || "./openclaw.json";
571
+ const client = new Sonzai({ apiKey: options.apiKey, baseUrl });
572
+ let agentId;
573
+ if (options.agentId) {
574
+ const agent = await client.agents.get(options.agentId);
575
+ agentId = agent.agent_id;
576
+ } else {
577
+ const agent = await client.agents.create({ name: agentName });
578
+ agentId = agent.agent_id;
579
+ }
580
+ const pluginConfig = buildOpenClawConfig(options.apiKey, agentId);
581
+ let written = false;
582
+ if (writeConfig) {
583
+ mergeOpenClawConfig(configPath, pluginConfig);
584
+ written = true;
585
+ }
586
+ return {
587
+ agentId,
588
+ agentName,
589
+ config: pluginConfig,
590
+ configPath: written ? path.resolve(configPath) : void 0,
591
+ written
592
+ };
593
+ }
594
+ function buildOpenClawConfig(_apiKey, agentId) {
595
+ return {
596
+ plugins: {
597
+ slots: {
598
+ contextEngine: "sonzai"
599
+ },
600
+ entries: {
601
+ sonzai: {
602
+ enabled: true,
603
+ agentId
604
+ // apiKey is intentionally NOT written to the config file —
605
+ // it should be set via SONZAI_API_KEY env var for security.
606
+ }
607
+ }
608
+ }
609
+ };
610
+ }
611
+ function mergeOpenClawConfig(configPath, pluginConfig) {
612
+ let existing = {};
613
+ const resolvedPath = path.resolve(configPath);
614
+ if (fs.existsSync(resolvedPath)) {
615
+ try {
616
+ const raw = fs.readFileSync(resolvedPath, "utf-8");
617
+ existing = JSON.parse(raw);
618
+ } catch {
619
+ }
620
+ }
621
+ const merged = { ...existing };
622
+ const existingPlugins = merged.plugins || {};
623
+ const newPlugins = pluginConfig.plugins;
624
+ const existingSlots = existingPlugins.slots || {};
625
+ const newSlots = newPlugins.slots || {};
626
+ const existingEntries = existingPlugins.entries || {};
627
+ const newEntries = newPlugins.entries || {};
628
+ merged.plugins = {
629
+ ...existingPlugins,
630
+ slots: { ...existingSlots, ...newSlots },
631
+ entries: { ...existingEntries, ...newEntries }
632
+ };
633
+ fs.writeFileSync(resolvedPath, JSON.stringify(merged, null, 2) + "\n");
634
+ }
635
+
636
+ export { SessionCache, SonzaiContextEngine, buildSystemPromptAddition, register as default, estimateTokens, parseSessionKey, resolveConfig, setup };
637
+ //# sourceMappingURL=index.js.map
638
+ //# sourceMappingURL=index.js.map