@byte5ai/palaia 2.3.6 → 2.7.1

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/tools.ts CHANGED
@@ -1,15 +1,16 @@
1
1
  /**
2
2
  * Agent tools: memory_search, memory_get, memory_write.
3
3
  *
4
- * These tools are the core of the Palaia OpenClaw integration.
4
+ * These tools are the core of the palaia OpenClaw integration.
5
5
  * They shell out to the palaia CLI with --json and return results
6
6
  * in the format OpenClaw agents expect.
7
7
  */
8
8
 
9
9
  import { Type } from "@sinclair/typebox";
10
- import { run, runJson, type RunnerOpts } from "./runner.js";
10
+ import { run, runJson, getEmbedServerManager, type RunnerOpts } from "./runner.js";
11
11
  import type { PalaiaPluginConfig } from "./config.js";
12
12
  import { sanitizeScope, isValidScope } from "./hooks/index.js";
13
+ import { loadPriorities, resolvePriorities } from "./priorities.js";
13
14
  import type { OpenClawPluginApi } from "./types.js";
14
15
 
15
16
  /** Shape returned by `palaia query --json` */
@@ -61,7 +62,7 @@ function buildRunnerOpts(config: PalaiaPluginConfig): RunnerOpts {
61
62
  }
62
63
 
63
64
  /**
64
- * Register all Palaia agent tools on the given plugin API.
65
+ * Register all palaia agent tools on the given plugin API.
65
66
  */
66
67
  export function registerTools(api: OpenClawPluginApi, config: PalaiaPluginConfig): void {
67
68
  const opts = buildRunnerOpts(config);
@@ -70,7 +71,7 @@ export function registerTools(api: OpenClawPluginApi, config: PalaiaPluginConfig
70
71
  api.registerTool({
71
72
  name: "memory_search",
72
73
  description:
73
- "Semantically search Palaia memory for relevant notes and context.",
74
+ "Semantically search palaia memory for relevant notes and context.",
74
75
  parameters: Type.Object({
75
76
  query: Type.String({ description: "Search query" }),
76
77
  maxResults: Type.Optional(
@@ -81,11 +82,6 @@ export function registerTools(api: OpenClawPluginApi, config: PalaiaPluginConfig
81
82
  description: "hot|warm|all (default: hot+warm)",
82
83
  })
83
84
  ),
84
- scope: Type.Optional(
85
- Type.String({
86
- description: "Filter by scope: private|team|shared:X|public",
87
- })
88
- ),
89
85
  type: Type.Optional(
90
86
  Type.String({
91
87
  description: "Filter by entry type: memory|process|task",
@@ -98,27 +94,73 @@ export function registerTools(api: OpenClawPluginApi, config: PalaiaPluginConfig
98
94
  query: string;
99
95
  maxResults?: number;
100
96
  tier?: string;
101
- scope?: string;
102
97
  type?: string;
103
98
  }
104
99
  ) {
100
+ // Load scope visibility from priorities (Issue #145: agent isolation)
101
+ let scopeVisibility: string[] | null = null;
102
+ try {
103
+ const prio = await loadPriorities(config.workspace || "");
104
+ const agentId = process.env.PALAIA_AGENT || undefined;
105
+ const resolvedPrio = resolvePriorities(prio, {
106
+ recallTypeWeight: config.recallTypeWeight,
107
+ recallMinScore: config.recallMinScore,
108
+ maxInjectedChars: config.maxInjectedChars,
109
+ tier: config.tier,
110
+ }, agentId);
111
+ scopeVisibility = resolvedPrio.scopeVisibility;
112
+ } catch {
113
+ // Non-fatal: proceed without scope filtering
114
+ }
115
+
105
116
  const limit = params.maxResults || config.maxResults || 5;
106
- const args: string[] = ["query", params.query, "--limit", String(limit)];
117
+ const includeCold = params.tier === "all" || config.tier === "all";
107
118
 
108
- // --all flag includes cold tier
109
- if (params.tier === "all" || config.tier === "all") {
110
- args.push("--all");
119
+ // Try embed server first — its queue correctly handles concurrent requests
120
+ // without counting wait time against the timeout.
121
+ let result: QueryResult | null = null;
122
+ if (config.embeddingServer) {
123
+ try {
124
+ const mgr = getEmbedServerManager(opts);
125
+ const resp = await mgr.query({
126
+ text: params.query,
127
+ top_k: limit,
128
+ include_cold: includeCold,
129
+ ...(params.type ? { type: params.type } : {}),
130
+ }, config.timeoutMs || 3000);
131
+ if (resp?.result?.results && Array.isArray(resp.result.results)) {
132
+ result = { results: resp.result.results };
133
+ }
134
+ } catch {
135
+ // Fall through to CLI
136
+ }
111
137
  }
112
138
 
113
- // Type filter (Issue #82)
114
- if (params.type) {
115
- args.push("--type", params.type);
139
+ // CLI fallback longer timeout since it includes process spawn overhead
140
+ if (!result) {
141
+ const args: string[] = ["query", params.query, "--limit", String(limit)];
142
+ if (includeCold) {
143
+ args.push("--all");
144
+ }
145
+ if (params.type) {
146
+ args.push("--type", params.type);
147
+ }
148
+ result = await runJson<QueryResult>(args, { ...opts, timeoutMs: 15000 });
116
149
  }
117
150
 
118
- const result = await runJson<QueryResult>(args, opts);
151
+ // Apply scope visibility filter (Issue #145: agent isolation)
152
+ let filteredResults = result.results || [];
153
+ if (scopeVisibility) {
154
+ filteredResults = filteredResults.filter((r) => {
155
+ const scope = r.scope || "team";
156
+ // Legacy shared:X entries are treated as team
157
+ const effectiveScope = scope.startsWith("shared:") ? "team" : scope;
158
+ return scopeVisibility!.includes(effectiveScope);
159
+ });
160
+ }
119
161
 
120
162
  // Format as memory_search compatible output
121
- const snippets = (result.results || []).map((r) => {
163
+ const snippets = filteredResults.map((r) => {
122
164
  const body = r.content || r.body || "";
123
165
  const path = r.path || `${r.tier}/${r.id}.md`;
124
166
  return {
@@ -149,7 +191,7 @@ export function registerTools(api: OpenClawPluginApi, config: PalaiaPluginConfig
149
191
  // ── memory_get ─────────────────────────────────────────────────
150
192
  api.registerTool({
151
193
  name: "memory_get",
152
- description: "Read a specific Palaia memory entry by path or id.",
194
+ description: "Read a specific palaia memory entry by path or id.",
153
195
  parameters: Type.Object({
154
196
  path: Type.String({ description: "Memory path or UUID" }),
155
197
  from: Type.Optional(
@@ -189,12 +231,12 @@ export function registerTools(api: OpenClawPluginApi, config: PalaiaPluginConfig
189
231
  {
190
232
  name: "memory_write",
191
233
  description:
192
- "Write a new memory entry to Palaia. WAL-backed, crash-safe.",
234
+ "Write a new memory entry to palaia. WAL-backed, crash-safe.",
193
235
  parameters: Type.Object({
194
236
  content: Type.String({ description: "Memory content to write" }),
195
237
  scope: Type.Optional(
196
238
  Type.String({
197
- description: "Scope: private|team|shared:X|public (default: team)",
239
+ description: "Scope: private|team|public (default: team)",
198
240
  default: "team",
199
241
  })
200
242
  ),
@@ -282,7 +324,7 @@ export function registerTools(api: OpenClawPluginApi, config: PalaiaPluginConfig
282
324
  content: [
283
325
  {
284
326
  type: "text" as const,
285
- text: `Invalid scope "${params.scope}". Valid scopes: private, team, public, shared:<name>`,
327
+ text: `Invalid scope "${params.scope}". Valid scopes: private, team, public`,
286
328
  },
287
329
  ],
288
330
  };
package/src/types.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  * They are maintained locally to avoid a build-time dependency on the
6
6
  * openclaw package (which is a peerDependency loaded at runtime).
7
7
  *
8
- * Based on OpenClaw v2026.3.22 plugin-sdk.
8
+ * Based on OpenClaw v2026.3.28 plugin-sdk.
9
9
  */
10
10
 
11
11
  import type { TObject } from "@sinclair/typebox";
@@ -28,23 +28,230 @@ export interface ToolOptions {
28
28
  optional?: boolean;
29
29
  }
30
30
 
31
- export type ToolFactory = ToolDefinition;
31
+ export type ToolFactory = ToolDefinition | ((ctx: Record<string, unknown>) => ToolDefinition | ToolDefinition[] | null);
32
32
 
33
- // ── Hook Types ──────────────────────────────────────────────────────────
33
+ // ── Hook Types ─────────────��────────────────────────────────────────────
34
34
 
35
+ /**
36
+ * All hook names supported by OpenClaw v2026.3.28.
37
+ * palaia registers handlers for a subset of these.
38
+ */
35
39
  export type HookName =
40
+ // Session lifecycle
41
+ | "session_start"
42
+ | "session_end"
43
+ // Agent & LLM
44
+ | "before_model_resolve"
36
45
  | "before_prompt_build"
46
+ | "before_agent_start"
47
+ | "llm_input"
48
+ | "llm_output"
37
49
  | "agent_end"
50
+ // Context management
51
+ | "before_compaction"
52
+ | "after_compaction"
53
+ | "before_reset"
54
+ // Messages
55
+ | "inbound_claim"
56
+ | "before_dispatch"
38
57
  | "message_received"
39
- | "message_sending";
58
+ | "message_sending"
59
+ | "message_sent"
60
+ // Tool execution
61
+ | "before_tool_call"
62
+ | "after_tool_call"
63
+ | "tool_result_persist"
64
+ | "before_message_write"
65
+ // Subagents
66
+ | "subagent_spawning"
67
+ | "subagent_delivery_target"
68
+ | "subagent_spawned"
69
+ | "subagent_ended"
70
+ // Gateway
71
+ | "gateway_start"
72
+ | "gateway_stop";
40
73
 
41
- export type HookHandler = (event: unknown, ctx: unknown) => void | Promise<unknown>;
74
+ export type HookHandler = (event: unknown, ctx: unknown) => unknown | Promise<unknown>;
42
75
 
43
76
  export interface HookOptions {
44
77
  priority?: number;
45
78
  }
46
79
 
47
- // ── Command Types ───────────────────────────────────────────────────────
80
+ // ── Hook Event Types ────────────────────────────────────────────────────
81
+
82
+ /** session_start event. */
83
+ export type SessionStartEvent = {
84
+ sessionId: string;
85
+ sessionKey?: string;
86
+ resumedFrom?: string;
87
+ };
88
+
89
+ /** session_end event. */
90
+ export type SessionEndEvent = {
91
+ sessionId: string;
92
+ sessionKey?: string;
93
+ messageCount: number;
94
+ durationMs?: number;
95
+ };
96
+
97
+ /** before_prompt_build event. */
98
+ export type BeforePromptBuildEvent = {
99
+ prompt: string;
100
+ messages: unknown[];
101
+ };
102
+
103
+ /** before_prompt_build result. */
104
+ export type BeforePromptBuildResult = {
105
+ systemPrompt?: string;
106
+ prependContext?: string;
107
+ prependSystemContext?: string;
108
+ appendSystemContext?: string;
109
+ };
110
+
111
+ /** before_reset event. */
112
+ export type BeforeResetEvent = {
113
+ sessionFile?: string;
114
+ messages?: unknown[];
115
+ reason?: string;
116
+ };
117
+
118
+ /** agent_end event. */
119
+ export type AgentEndEvent = {
120
+ messages: unknown[];
121
+ success: boolean;
122
+ error?: string;
123
+ durationMs?: number;
124
+ };
125
+
126
+ /** llm_input event — fired before each LLM call. */
127
+ export type LlmInputEvent = {
128
+ runId: string;
129
+ sessionId: string;
130
+ provider: string;
131
+ model: string;
132
+ systemPrompt?: string;
133
+ prompt: string;
134
+ historyMessages: unknown[];
135
+ imagesCount: number;
136
+ };
137
+
138
+ /** llm_output event — fired after each LLM response. */
139
+ export type LlmOutputEvent = {
140
+ runId: string;
141
+ sessionId: string;
142
+ provider: string;
143
+ model: string;
144
+ assistantTexts: string[];
145
+ lastAssistant?: unknown;
146
+ usage?: {
147
+ input?: number;
148
+ output?: number;
149
+ cacheRead?: number;
150
+ cacheWrite?: number;
151
+ total?: number;
152
+ };
153
+ };
154
+
155
+ /** after_tool_call event. */
156
+ export type AfterToolCallEvent = {
157
+ toolName: string;
158
+ params: Record<string, unknown>;
159
+ runId?: string;
160
+ toolCallId?: string;
161
+ result?: unknown;
162
+ error?: string;
163
+ durationMs?: number;
164
+ };
165
+
166
+ /** message_received event. */
167
+ export type MessageReceivedEvent = {
168
+ from: string;
169
+ content: string;
170
+ timestamp?: number;
171
+ metadata?: Record<string, unknown>;
172
+ };
173
+
174
+ /** message_sending event. */
175
+ export type MessageSendingEvent = {
176
+ to: string;
177
+ content: string;
178
+ metadata?: Record<string, unknown>;
179
+ };
180
+
181
+ /** message_sending result. */
182
+ export type MessageSendingResult = {
183
+ content?: string;
184
+ cancel?: boolean;
185
+ };
186
+
187
+ /** subagent_spawning event. */
188
+ export type SubagentSpawningEvent = {
189
+ childSessionKey: string;
190
+ agentId: string;
191
+ label?: string;
192
+ mode: "run" | "session";
193
+ requester?: {
194
+ channel?: string;
195
+ accountId?: string;
196
+ to?: string;
197
+ threadId?: string | number;
198
+ };
199
+ threadRequested: boolean;
200
+ };
201
+
202
+ /** subagent_ended event. */
203
+ export type SubagentEndedEvent = {
204
+ targetSessionKey: string;
205
+ targetKind: "subagent" | "acp";
206
+ reason: string;
207
+ sendFarewell?: boolean;
208
+ accountId?: string;
209
+ runId?: string;
210
+ endedAt?: number;
211
+ outcome?: "ok" | "error" | "timeout" | "killed" | "reset" | "deleted";
212
+ error?: string;
213
+ };
214
+
215
+ // ── Hook Context Types ──────────────��───────────────────────────────────
216
+
217
+ export type AgentContext = {
218
+ agentId?: string;
219
+ sessionKey?: string;
220
+ sessionId?: string;
221
+ workspaceDir?: string;
222
+ messageProvider?: string;
223
+ trigger?: string;
224
+ channelId?: string;
225
+ };
226
+
227
+ export type MessageContext = {
228
+ channelId: string;
229
+ accountId?: string;
230
+ conversationId?: string;
231
+ };
232
+
233
+ export type SessionContext = {
234
+ agentId?: string;
235
+ sessionId: string;
236
+ sessionKey?: string;
237
+ };
238
+
239
+ export type ToolContext = {
240
+ agentId?: string;
241
+ sessionKey?: string;
242
+ sessionId?: string;
243
+ runId?: string;
244
+ toolName: string;
245
+ toolCallId?: string;
246
+ };
247
+
248
+ export type SubagentContext = {
249
+ runId?: string;
250
+ childSessionKey?: string;
251
+ requesterSessionKey?: string;
252
+ };
253
+
254
+ // ── Command Types ───���───────────────────────────────────────────────────
48
255
 
49
256
  export interface CommandDefinition {
50
257
  name: string;
@@ -52,17 +259,156 @@ export interface CommandDefinition {
52
259
  handler(args: string): Promise<{ text: string }> | { text: string };
53
260
  }
54
261
 
55
- // ── Service Types ───────────────────────────────────────────────────────
262
+ // ── Service Types ─────────────────────────────────────���─────────────────
263
+
264
+ export interface ServiceContext {
265
+ config: Record<string, unknown>;
266
+ workspaceDir?: string;
267
+ stateDir: string;
268
+ logger: PluginLogger;
269
+ }
56
270
 
57
271
  export interface ServiceDefinition {
58
272
  id: string;
59
- start(): Promise<void>;
60
- stop?(): Promise<void>;
273
+ start(ctx: ServiceContext): Promise<void>;
274
+ stop?(ctx: ServiceContext): Promise<void>;
61
275
  }
62
276
 
63
- // ── Context Engine Types ────────────────────────────────────────────────
277
+ // ���─ Context Engine Types ────────────���───────────────────────────────────
278
+
279
+ /** Opaque message type from OpenClaw's agent runtime. */
280
+ export type AgentMessage = unknown;
281
+
282
+ export interface ContextEngineInfo {
283
+ id: string;
284
+ name: string;
285
+ version?: string;
286
+ ownsCompaction?: boolean;
287
+ }
64
288
 
289
+ export type BootstrapResult = {
290
+ bootstrapped: boolean;
291
+ importedMessages?: number;
292
+ reason?: string;
293
+ };
294
+
295
+ export type IngestResult = {
296
+ ingested: boolean;
297
+ };
298
+
299
+ export type AssembleResult = {
300
+ messages: AgentMessage[];
301
+ estimatedTokens: number;
302
+ systemPromptAddition?: string;
303
+ };
304
+
305
+ export type CompactResult = {
306
+ ok: boolean;
307
+ compacted: boolean;
308
+ reason?: string;
309
+ result?: {
310
+ summary?: string;
311
+ firstKeptEntryId?: string;
312
+ tokensBefore: number;
313
+ tokensAfter?: number;
314
+ details?: unknown;
315
+ };
316
+ };
317
+
318
+ export type SubagentSpawnPreparation = {
319
+ rollback: () => void | Promise<void>;
320
+ };
321
+
322
+ export type SubagentEndReason = "deleted" | "completed" | "swept" | "released";
323
+
324
+ /**
325
+ * ContextEngine interface — matches OpenClaw v2026.3.28.
326
+ *
327
+ * This is the full interface. palaia implements a subset;
328
+ * optional methods are marked with `?`.
329
+ */
65
330
  export interface ContextEngine {
331
+ readonly info: ContextEngineInfo;
332
+
333
+ bootstrap?(params: {
334
+ sessionId: string;
335
+ sessionKey?: string;
336
+ sessionFile: string;
337
+ }): Promise<BootstrapResult>;
338
+
339
+ maintain?(params: {
340
+ sessionId: string;
341
+ sessionKey?: string;
342
+ sessionFile: string;
343
+ runtimeContext?: Record<string, unknown>;
344
+ }): Promise<unknown>;
345
+
346
+ ingest(params: {
347
+ sessionId: string;
348
+ sessionKey?: string;
349
+ message: AgentMessage;
350
+ isHeartbeat?: boolean;
351
+ }): Promise<IngestResult>;
352
+
353
+ ingestBatch?(params: {
354
+ sessionId: string;
355
+ sessionKey?: string;
356
+ messages: AgentMessage[];
357
+ isHeartbeat?: boolean;
358
+ }): Promise<{ ingestedCount: number }>;
359
+
360
+ afterTurn?(params: {
361
+ sessionId: string;
362
+ sessionKey?: string;
363
+ sessionFile: string;
364
+ messages: AgentMessage[];
365
+ prePromptMessageCount: number;
366
+ autoCompactionSummary?: string;
367
+ isHeartbeat?: boolean;
368
+ tokenBudget?: number;
369
+ runtimeContext?: Record<string, unknown>;
370
+ }): Promise<void>;
371
+
372
+ assemble(params: {
373
+ sessionId: string;
374
+ sessionKey?: string;
375
+ messages: AgentMessage[];
376
+ tokenBudget?: number;
377
+ model?: string;
378
+ prompt?: string;
379
+ }): Promise<AssembleResult>;
380
+
381
+ compact(params: {
382
+ sessionId: string;
383
+ sessionKey?: string;
384
+ sessionFile: string;
385
+ tokenBudget?: number;
386
+ force?: boolean;
387
+ currentTokenCount?: number;
388
+ compactionTarget?: "budget" | "threshold";
389
+ customInstructions?: string;
390
+ runtimeContext?: Record<string, unknown>;
391
+ }): Promise<CompactResult>;
392
+
393
+ prepareSubagentSpawn?(params: {
394
+ parentSessionKey: string;
395
+ childSessionKey: string;
396
+ ttlMs?: number;
397
+ }): Promise<SubagentSpawnPreparation | undefined>;
398
+
399
+ onSubagentEnded?(params: {
400
+ childSessionKey: string;
401
+ reason: SubagentEndReason;
402
+ }): Promise<void>;
403
+
404
+ dispose?(): Promise<void>;
405
+ }
406
+
407
+ /**
408
+ * Legacy ContextEngine interface for backward compatibility.
409
+ * Used when OpenClaw < 2026.3.24 registers via the old signature.
410
+ */
411
+ export interface LegacyContextEngine {
66
412
  bootstrap?(): Promise<void>;
67
413
  ingest?(messages: unknown[]): Promise<void>;
68
414
  assemble(budget: { maxTokens: number }): Promise<{ content: string; tokenEstimate: number }>;
@@ -72,45 +418,86 @@ export interface ContextEngine {
72
418
  onSubagentEnded?(result: unknown): Promise<void>;
73
419
  }
74
420
 
75
- // ── Logger ──────────────────────────────────────────────────────────────
421
+ export type ContextEngineFactory = () => ContextEngine | Promise<ContextEngine>;
422
+
423
+ // ── Memory Prompt Section ───────────────────────────────────────────────
424
+
425
+ export type MemoryPromptSectionBuilder = (params: {
426
+ availableTools: Set<string>;
427
+ citationsMode?: string;
428
+ }) => string[];
429
+
430
+ // ── Logger ──────────────���───────────────────────────────────────────────
76
431
 
77
432
  export interface PluginLogger {
78
- info(...args: unknown[]): void;
79
- warn(...args: unknown[]): void;
80
- error?(...args: unknown[]): void;
81
- debug?(...args: unknown[]): void;
433
+ info(message: string): void;
434
+ warn(message: string): void;
435
+ error(message: string): void;
436
+ debug?(message: string): void;
82
437
  }
83
438
 
84
- // ── Runtime ─────────────────────────────────────────────────────────────
439
+ // ���─ Runtime ───────────────��─────────────────────────────���───────────────
85
440
 
86
441
  export interface PluginRuntime {
442
+ version?: string;
87
443
  agent?: {
88
444
  runEmbeddedPiAgent?(params: Record<string, unknown>): Promise<unknown>;
445
+ session?: {
446
+ resolveStorePath?(...args: unknown[]): string;
447
+ loadSessionStore?(...args: unknown[]): Promise<unknown>;
448
+ };
449
+ };
450
+ subagent?: {
451
+ run?(params: {
452
+ sessionKey: string;
453
+ message: string;
454
+ provider?: string;
455
+ model?: string;
456
+ extraSystemPrompt?: string;
457
+ }): Promise<{ runId: string }>;
458
+ getSessionMessages?(params: {
459
+ sessionKey: string;
460
+ limit?: number;
461
+ }): Promise<{ messages: unknown[] }>;
89
462
  };
90
463
  modelAuth?: {
91
464
  resolveApiKeyForProvider(params: {
92
465
  provider: string;
93
- cfg: Record<string, unknown>;
466
+ cfg?: Record<string, unknown>;
94
467
  }): Promise<string | null>;
95
468
  };
469
+ system?: {
470
+ requestHeartbeatNow?(): void;
471
+ };
472
+ events?: {
473
+ onSessionTranscriptUpdate?(handler: (event: unknown) => void): void;
474
+ };
96
475
  }
97
476
 
98
477
  // ── Main Plugin API ─────────────────────────────────────────────────────
99
478
 
100
479
  export interface OpenClawPluginApi {
101
- registerTool(definition: ToolDefinition, options?: ToolOptions): void;
480
+ id: string;
481
+ name: string;
482
+ version?: string;
483
+ source?: string;
484
+ registrationMode?: string;
485
+
486
+ registerTool(definition: ToolDefinition | ToolFactory, options?: ToolOptions): void;
102
487
  registerCommand(command: CommandDefinition): void;
103
488
  registerService(service: ServiceDefinition): void;
104
- registerContextEngine?(id: string, engine: ContextEngine): void;
105
- on(hook: HookName | string, handler: HookHandler): void;
106
- getConfig(pluginId: string): Record<string, unknown> | undefined;
489
+ registerContextEngine?(id: string, factory: ContextEngineFactory): void;
490
+ registerMemoryPromptSection?(builder: MemoryPromptSectionBuilder): void;
491
+ registerHook?(events: string | string[], handler: HookHandler, opts?: HookOptions): void;
492
+ on(hook: HookName | string, handler: HookHandler, opts?: HookOptions): void;
107
493
  logger: PluginLogger;
108
494
  runtime: PluginRuntime;
109
495
  config: Record<string, unknown>;
496
+ pluginConfig?: Record<string, unknown>;
110
497
  workspace?: { dir: string; agentId?: string } | string;
111
498
  }
112
499
 
113
- // ── Plugin Entry ────────────────────────────────────────────────────────
500
+ // ── Plugin Entry ────────���───────────────────────────────────────────────
114
501
 
115
502
  export interface OpenClawPluginEntry {
116
503
  id: string;