@phronesis-io/openclaw-eigenflux 0.0.1 → 0.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.
Files changed (44) hide show
  1. package/README.md +16 -3
  2. package/dist/config.d.ts +12 -5
  3. package/dist/config.d.ts.map +1 -1
  4. package/dist/config.js +47 -10
  5. package/dist/config.js.map +1 -1
  6. package/dist/index.d.ts +5 -3
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +3 -7
  9. package/dist/index.js.map +1 -1
  10. package/dist/notifier.d.ts +1 -17
  11. package/dist/notifier.d.ts.map +1 -1
  12. package/dist/notifier.js +1 -94
  13. package/dist/notifier.js.map +1 -1
  14. package/dist/pm-polling-client.d.ts +1 -0
  15. package/dist/pm-polling-client.d.ts.map +1 -1
  16. package/dist/pm-polling-client.js +64 -57
  17. package/dist/pm-polling-client.js.map +1 -1
  18. package/dist/polling-client.d.ts +1 -0
  19. package/dist/polling-client.d.ts.map +1 -1
  20. package/dist/polling-client.js +65 -58
  21. package/dist/polling-client.js.map +1 -1
  22. package/openclaw.plugin.json +8 -6
  23. package/package.json +2 -2
  24. package/src/agent-prompt-templates.ts +0 -91
  25. package/src/config.test.ts +0 -188
  26. package/src/config.ts +0 -410
  27. package/src/credentials-loader.test.ts +0 -78
  28. package/src/credentials-loader.ts +0 -121
  29. package/src/gateway-rpc-client.test.ts +0 -190
  30. package/src/gateway-rpc-client.ts +0 -373
  31. package/src/index.integration.test.ts +0 -437
  32. package/src/index.test.ts +0 -454
  33. package/src/index.ts +0 -758
  34. package/src/logger.ts +0 -27
  35. package/src/notification-route-resolver.test.ts +0 -136
  36. package/src/notification-route-resolver.ts +0 -430
  37. package/src/notifier.test.ts +0 -374
  38. package/src/notifier.ts +0 -558
  39. package/src/openclaw-plugin-sdk.d.ts +0 -121
  40. package/src/pm-polling-client.test.ts +0 -390
  41. package/src/pm-polling-client.ts +0 -257
  42. package/src/polling-client.test.ts +0 -279
  43. package/src/polling-client.ts +0 -283
  44. package/src/session-route-memory.ts +0 -106
package/src/notifier.ts DELETED
@@ -1,558 +0,0 @@
1
- import { spawn } from 'node:child_process';
2
- import { randomUUID } from 'node:crypto';
3
- import type { OpenClawPluginApi } from 'openclaw/plugin-sdk';
4
- import { type NotificationRouteOverrides } from './config';
5
- import { OpenClawGatewayRpcClient } from './gateway-rpc-client';
6
- import { Logger } from './logger';
7
- import {
8
- resolveNotificationRoute,
9
- type NotificationRouteConfig,
10
- type ResolvedNotificationRoute,
11
- } from './notification-route-resolver';
12
- import { writeStoredNotificationRoute } from './session-route-memory';
13
-
14
- const COMMAND_TIMEOUT_MS = 15000;
15
- const HEARTBEAT_REASON = 'plugin:eigenflux';
16
-
17
- export type EigenFluxNotifierConfig = {
18
- gatewayUrl: string;
19
- gatewayToken?: string;
20
- workdir?: string;
21
- sessionKey: string;
22
- agentId: string;
23
- replyChannel?: string;
24
- replyTo?: string;
25
- replyAccountId?: string;
26
- openclawCliBin: string;
27
- sessionStorePath?: string;
28
- routeOverrides?: NotificationRouteOverrides;
29
- };
30
-
31
- type GatewayRpcClientLike = {
32
- sendAgentMessage(message: string): Promise<{ sessionKey: string; runId: string }>;
33
- };
34
-
35
- type CreateGatewayRpcClient = (
36
- config: EigenFluxNotifierConfig & ResolvedNotificationRoute,
37
- logger: Logger
38
- ) => GatewayRpcClientLike;
39
-
40
- type CommandRunner = (
41
- argv: string[],
42
- options: { timeoutMs: number }
43
- ) => Promise<{ code: number | null; stdout?: string; stderr?: string }>;
44
-
45
- type SpawnRunner = (argv: string[], timeoutMs: number) => Promise<NotifyAttemptResult>;
46
-
47
- type NotifyAttemptResult =
48
- | {
49
- ok: true;
50
- mode: string;
51
- sessionKey?: string;
52
- runId?: string;
53
- detail?: string;
54
- }
55
- | {
56
- ok: false;
57
- mode: string;
58
- error: string;
59
- };
60
-
61
- export class EigenFluxNotifier {
62
- private readonly api: OpenClawPluginApi;
63
- private readonly logger: Logger;
64
- private readonly config: EigenFluxNotifierConfig;
65
- private readonly createGatewayRpcClient: CreateGatewayRpcClient;
66
- private readonly spawnRunner: SpawnRunner;
67
-
68
- constructor(
69
- api: OpenClawPluginApi,
70
- logger: Logger,
71
- config: EigenFluxNotifierConfig,
72
- createGatewayRpcClient: CreateGatewayRpcClient = createDefaultGatewayRpcClient,
73
- spawnRunner: SpawnRunner = runSpawnCommand
74
- ) {
75
- this.api = api;
76
- this.logger = logger;
77
- this.config = config;
78
- this.createGatewayRpcClient = createGatewayRpcClient;
79
- this.spawnRunner = spawnRunner;
80
- }
81
-
82
- async deliver(message: string): Promise<boolean> {
83
- const route = this.resolveRoute();
84
- const attempts: Array<() => Promise<NotifyAttemptResult>> = [
85
- () => this.tryNotifyViaRuntimeSubagent(message, route),
86
- () => this.tryNotifyViaGatewayRpcAgent(message, route),
87
- () => this.tryNotifyViaRuntimeCommandAgent(message, route),
88
- () => this.tryNotifyViaSpawnAgent(message, route),
89
- () => this.tryNotifyViaRuntimeHeartbeat(message, route),
90
- () => this.tryNotifyViaRuntimeCommandHeartbeat(message),
91
- () => this.tryNotifyViaSpawnHeartbeat(message),
92
- ];
93
-
94
- const errors: string[] = [];
95
-
96
- for (const attempt of attempts) {
97
- const result = await attempt();
98
- if (result.ok) {
99
- this.rememberRoute({
100
- sessionKey: result.sessionKey ?? route.sessionKey,
101
- agentId: route.agentId,
102
- replyChannel: route.replyChannel,
103
- replyTo: route.replyTo,
104
- replyAccountId: route.replyAccountId,
105
- });
106
- this.logDispatch(result);
107
- return true;
108
- }
109
- errors.push(`${result.mode}: ${result.error}`);
110
- }
111
-
112
- this.logger.error(`Failed to deliver notification: ${errors.join(' | ')}`);
113
- return false;
114
- }
115
-
116
- async deliverWithSubagent(message: string): Promise<boolean> {
117
- const route = this.resolveRoute();
118
- const result = await this.tryNotifyViaRuntimeSubagent(message, route);
119
- if (!result.ok) {
120
- this.logger.warn(`runtime.subagent test dispatch failed: ${result.error}`);
121
- return false;
122
- }
123
- this.rememberRoute(route);
124
- this.logDispatch(result);
125
- return true;
126
- }
127
-
128
- private async tryNotifyViaRuntimeSubagent(
129
- message: string,
130
- route: ResolvedNotificationRoute
131
- ): Promise<NotifyAttemptResult> {
132
- const runtimeSubagent = (this.api.runtime as
133
- | {
134
- subagent?: {
135
- run?: (params: {
136
- sessionKey: string;
137
- message: string;
138
- deliver?: boolean;
139
- idempotencyKey?: string;
140
- }) => Promise<{ runId: string }>;
141
- };
142
- }
143
- | undefined)?.subagent;
144
-
145
- if (!runtimeSubagent || typeof runtimeSubagent.run !== 'function') {
146
- return {
147
- ok: false,
148
- mode: 'runtime.subagent',
149
- error: 'runtime.subagent.run is unavailable',
150
- };
151
- }
152
-
153
- try {
154
- const result = await runtimeSubagent.run({
155
- sessionKey: route.sessionKey,
156
- message,
157
- deliver: true,
158
- idempotencyKey: randomUUID(),
159
- });
160
- return {
161
- ok: true,
162
- mode: 'runtime.subagent',
163
- sessionKey: route.sessionKey,
164
- runId: result.runId,
165
- };
166
- } catch (error) {
167
- return {
168
- ok: false,
169
- mode: 'runtime.subagent',
170
- error: formatError(error),
171
- };
172
- }
173
- }
174
-
175
- private async tryNotifyViaGatewayRpcAgent(
176
- message: string,
177
- route: ResolvedNotificationRoute
178
- ): Promise<NotifyAttemptResult> {
179
- try {
180
- const client = this.createGatewayRpcClient(
181
- {
182
- ...this.config,
183
- ...route,
184
- },
185
- this.logger
186
- );
187
- const result = await client.sendAgentMessage(message);
188
- return {
189
- ok: true,
190
- mode: 'gateway.rpc.agent',
191
- sessionKey: result.sessionKey,
192
- runId: result.runId,
193
- };
194
- } catch (error) {
195
- return {
196
- ok: false,
197
- mode: 'gateway.rpc.agent',
198
- error: formatError(error),
199
- };
200
- }
201
- }
202
-
203
- private async tryNotifyViaRuntimeCommandAgent(
204
- message: string,
205
- route: ResolvedNotificationRoute
206
- ): Promise<NotifyAttemptResult> {
207
- return this.runRuntimeCommand(
208
- 'runtime.command.agent',
209
- this.buildAgentCliArgs(message, route),
210
- route
211
- );
212
- }
213
-
214
- private async tryNotifyViaSpawnAgent(
215
- message: string,
216
- route: ResolvedNotificationRoute
217
- ): Promise<NotifyAttemptResult> {
218
- return this.runSpawnCommand('spawn.agent', this.buildAgentCliArgs(message, route), route);
219
- }
220
-
221
- private async tryNotifyViaRuntimeHeartbeat(
222
- message: string,
223
- route: ResolvedNotificationRoute
224
- ): Promise<NotifyAttemptResult> {
225
- const runtimeSystem = (this.api.runtime as
226
- | {
227
- system?: {
228
- enqueueSystemEvent?: (
229
- text: string,
230
- options: {
231
- sessionKey: string;
232
- deliveryContext?: {
233
- channel?: string;
234
- to?: string;
235
- accountId?: string;
236
- };
237
- }
238
- ) => boolean;
239
- requestHeartbeatNow?: (options: {
240
- reason?: string;
241
- coalesceMs?: number;
242
- agentId?: string;
243
- sessionKey?: string;
244
- }) => void;
245
- };
246
- }
247
- | undefined)?.system;
248
-
249
- if (
250
- !runtimeSystem ||
251
- typeof runtimeSystem.enqueueSystemEvent !== 'function' ||
252
- typeof runtimeSystem.requestHeartbeatNow !== 'function'
253
- ) {
254
- return {
255
- ok: false,
256
- mode: 'runtime.system.heartbeat',
257
- error: 'runtime.system heartbeat APIs are unavailable',
258
- };
259
- }
260
-
261
- try {
262
- const enqueued = runtimeSystem.enqueueSystemEvent(message, {
263
- sessionKey: route.sessionKey,
264
- ...(this.resolveHeartbeatDeliveryContext(route)
265
- ? { deliveryContext: this.resolveHeartbeatDeliveryContext(route) }
266
- : {}),
267
- });
268
- runtimeSystem.requestHeartbeatNow({
269
- reason: HEARTBEAT_REASON,
270
- coalesceMs: 0,
271
- agentId: route.agentId,
272
- sessionKey: route.sessionKey,
273
- });
274
-
275
- return {
276
- ok: true,
277
- mode: 'runtime.system.heartbeat',
278
- sessionKey: route.sessionKey,
279
- detail: enqueued ? 'enqueued' : 'duplicate-enqueued',
280
- };
281
- } catch (error) {
282
- return {
283
- ok: false,
284
- mode: 'runtime.system.heartbeat',
285
- error: formatError(error),
286
- };
287
- }
288
- }
289
-
290
- private async tryNotifyViaRuntimeCommandHeartbeat(message: string): Promise<NotifyAttemptResult> {
291
- return this.runRuntimeCommand(
292
- 'runtime.command.heartbeat',
293
- this.buildHeartbeatCliArgs(message),
294
- this.resolveRoute()
295
- );
296
- }
297
-
298
- private async tryNotifyViaSpawnHeartbeat(message: string): Promise<NotifyAttemptResult> {
299
- return this.runSpawnCommand(
300
- 'spawn.heartbeat',
301
- this.buildHeartbeatCliArgs(message),
302
- this.resolveRoute()
303
- );
304
- }
305
-
306
- private async runRuntimeCommand(
307
- mode: string,
308
- argv: string[],
309
- route: ResolvedNotificationRoute
310
- ): Promise<NotifyAttemptResult> {
311
- const runtimeCommand = (this.api.runtime as
312
- | {
313
- system?: {
314
- runCommandWithTimeout?: CommandRunner;
315
- };
316
- }
317
- | undefined)?.system?.runCommandWithTimeout;
318
-
319
- if (typeof runtimeCommand !== 'function') {
320
- return {
321
- ok: false,
322
- mode,
323
- error: 'runtime.system.runCommandWithTimeout is unavailable',
324
- };
325
- }
326
-
327
- try {
328
- const result = await runtimeCommand(argv, { timeoutMs: COMMAND_TIMEOUT_MS });
329
- if (result.code === 0) {
330
- return {
331
- ok: true,
332
- mode,
333
- sessionKey: route.sessionKey,
334
- };
335
- }
336
-
337
- return {
338
- ok: false,
339
- mode,
340
- error: formatCommandFailure(result),
341
- };
342
- } catch (error) {
343
- return {
344
- ok: false,
345
- mode,
346
- error: formatError(error),
347
- };
348
- }
349
- }
350
-
351
- private async runSpawnCommand(
352
- mode: string,
353
- argv: string[],
354
- route: ResolvedNotificationRoute
355
- ): Promise<NotifyAttemptResult> {
356
- const [command, ...args] = argv;
357
- if (!command) {
358
- return {
359
- ok: false,
360
- mode,
361
- error: 'missing command',
362
- };
363
- }
364
-
365
- const result = await this.spawnRunner(argv, COMMAND_TIMEOUT_MS);
366
- return result.ok
367
- ? {
368
- ...result,
369
- mode,
370
- sessionKey: result.sessionKey ?? route.sessionKey,
371
- }
372
- : {
373
- ...result,
374
- mode,
375
- };
376
- }
377
-
378
- private buildAgentCliArgs(message: string, route: ResolvedNotificationRoute): string[] {
379
- const args = [
380
- this.config.openclawCliBin,
381
- 'agent',
382
- '--message',
383
- message,
384
- '--agent',
385
- route.agentId,
386
- '--deliver',
387
- ];
388
-
389
- if (route.replyChannel) {
390
- args.push('--reply-channel', route.replyChannel);
391
- }
392
- if (route.replyTo) {
393
- args.push('--reply-to', route.replyTo);
394
- }
395
- if (route.replyAccountId) {
396
- args.push('--reply-account', route.replyAccountId);
397
- }
398
-
399
- return args;
400
- }
401
-
402
- private buildHeartbeatCliArgs(message: string): string[] {
403
- return [
404
- this.config.openclawCliBin,
405
- 'system',
406
- 'event',
407
- '--text',
408
- message,
409
- '--mode',
410
- 'now',
411
- ];
412
- }
413
-
414
- private resolveHeartbeatDeliveryContext(
415
- route: ResolvedNotificationRoute
416
- ):
417
- | {
418
- channel?: string;
419
- to?: string;
420
- accountId?: string;
421
- }
422
- | undefined {
423
- if (!route.replyChannel && !route.replyTo && !route.replyAccountId) {
424
- return undefined;
425
- }
426
-
427
- return {
428
- ...(route.replyChannel ? { channel: route.replyChannel } : {}),
429
- ...(route.replyTo ? { to: route.replyTo } : {}),
430
- ...(route.replyAccountId ? { accountId: route.replyAccountId } : {}),
431
- };
432
- }
433
-
434
- private resolveRoute(): ResolvedNotificationRoute {
435
- return resolveNotificationRoute(this.config as NotificationRouteConfig, this.logger);
436
- }
437
-
438
- private rememberRoute(route: ResolvedNotificationRoute): void {
439
- if (!route.sessionKey || !route.agentId) {
440
- return;
441
- }
442
- writeStoredNotificationRoute(this.config.workdir, route, this.logger);
443
- }
444
-
445
- private logDispatch(result: Extract<NotifyAttemptResult, { ok: true }>): void {
446
- const details = [
447
- `mode=${result.mode}`,
448
- result.sessionKey ? `session_key=${result.sessionKey}` : null,
449
- result.runId ? `run_id=${result.runId}` : null,
450
- result.detail ? `detail=${result.detail}` : null,
451
- ]
452
- .filter(Boolean)
453
- .join(', ');
454
- this.logger.info(`Notification dispatched: ${details}`);
455
- }
456
- }
457
-
458
- function createDefaultGatewayRpcClient(
459
- config: EigenFluxNotifierConfig & ResolvedNotificationRoute,
460
- logger: Logger
461
- ): OpenClawGatewayRpcClient {
462
- return new OpenClawGatewayRpcClient({
463
- gatewayUrl: config.gatewayUrl,
464
- gatewayToken: config.gatewayToken,
465
- sessionKey: config.sessionKey,
466
- agentId: config.agentId,
467
- replyChannel: config.replyChannel,
468
- replyTo: config.replyTo,
469
- replyAccountId: config.replyAccountId,
470
- logger,
471
- });
472
- }
473
-
474
- function formatCommandFailure(result: {
475
- code: number | null;
476
- stdout?: string;
477
- stderr?: string;
478
- }): string {
479
- return (
480
- result.stderr?.trim() ||
481
- result.stdout?.trim() ||
482
- `command exited with ${result.code ?? 'unknown'}`
483
- );
484
- }
485
-
486
- function formatError(error: unknown): string {
487
- if (error instanceof Error) {
488
- return `${error.name}: ${error.message}`;
489
- }
490
- return String(error);
491
- }
492
-
493
- async function runSpawnCommand(argv: string[], timeoutMs: number): Promise<NotifyAttemptResult> {
494
- const [command, ...args] = argv;
495
- if (!command) {
496
- return {
497
- ok: false,
498
- mode: 'spawn',
499
- error: 'missing command',
500
- };
501
- }
502
-
503
- return await new Promise<NotifyAttemptResult>((resolve) => {
504
- const child = spawn(command, args, {
505
- stdio: ['ignore', 'pipe', 'pipe'],
506
- });
507
- let stdout = '';
508
- let stderr = '';
509
- let settled = false;
510
-
511
- const finish = (result: NotifyAttemptResult) => {
512
- if (settled) {
513
- return;
514
- }
515
- settled = true;
516
- resolve(result);
517
- };
518
-
519
- const timer = setTimeout(() => {
520
- child.kill();
521
- finish({
522
- ok: false,
523
- mode: 'spawn',
524
- error: `spawn timed out after ${timeoutMs}ms`,
525
- });
526
- }, timeoutMs);
527
-
528
- child.stdout.on('data', (chunk) => {
529
- stdout += chunk.toString();
530
- });
531
- child.stderr.on('data', (chunk) => {
532
- stderr += chunk.toString();
533
- });
534
- child.on('error', (error) => {
535
- clearTimeout(timer);
536
- finish({
537
- ok: false,
538
- mode: 'spawn',
539
- error: `spawn failed: ${error.message}`,
540
- });
541
- });
542
- child.on('close', (code) => {
543
- clearTimeout(timer);
544
- if (code === 0) {
545
- finish({
546
- ok: true,
547
- mode: 'spawn',
548
- });
549
- return;
550
- }
551
- finish({
552
- ok: false,
553
- mode: 'spawn',
554
- error: (stderr || stdout || `exit code ${code}`).trim(),
555
- });
556
- });
557
- });
558
- }
@@ -1,121 +0,0 @@
1
- declare module 'openclaw/plugin-sdk' {
2
- export interface OpenClawPluginConfigSchema {
3
- type: string;
4
- additionalProperties?: boolean;
5
- properties?: Record<string, unknown>;
6
- required?: string[];
7
- }
8
-
9
- export interface PluginLogger {
10
- info: (message: string, ...args: unknown[]) => void;
11
- warn: (message: string, ...args: unknown[]) => void;
12
- error: (message: string, ...args: unknown[]) => void;
13
- debug: (message: string, ...args: unknown[]) => void;
14
- }
15
-
16
- export interface OpenClawPluginService {
17
- id: string;
18
- start: () => Promise<void> | void;
19
- stop: () => Promise<void> | void;
20
- }
21
-
22
- export interface OpenClawPluginCommandContext {
23
- senderId?: string;
24
- channel?: string;
25
- channelId?: string;
26
- isAuthorizedSender?: boolean;
27
- args?: string;
28
- commandBody?: string;
29
- config?: Record<string, unknown>;
30
- from?: string;
31
- to?: string;
32
- accountId?: string;
33
- messageThreadId?: string | number;
34
- requestConversationBinding?: (params?: {
35
- summary?: string;
36
- detachHint?: string;
37
- }) => Promise<
38
- | {
39
- status: 'bound';
40
- binding: {
41
- bindingId: string;
42
- pluginId: string;
43
- pluginName?: string;
44
- pluginRoot: string;
45
- channel: string;
46
- accountId: string;
47
- conversationId: string;
48
- parentConversationId?: string;
49
- threadId?: string | number;
50
- boundAt: number;
51
- summary?: string;
52
- detachHint?: string;
53
- };
54
- }
55
- | {
56
- status: 'pending';
57
- approvalId: string;
58
- reply: {
59
- text?: string;
60
- };
61
- }
62
- | {
63
- status: 'error';
64
- message: string;
65
- }
66
- >;
67
- detachConversationBinding?: () => Promise<{ removed: boolean }>;
68
- getCurrentConversationBinding?: () => Promise<{
69
- bindingId: string;
70
- pluginId: string;
71
- pluginName?: string;
72
- pluginRoot: string;
73
- channel: string;
74
- accountId: string;
75
- conversationId: string;
76
- parentConversationId?: string;
77
- threadId?: string | number;
78
- boundAt: number;
79
- summary?: string;
80
- detachHint?: string;
81
- } | null>;
82
- }
83
-
84
- export interface OpenClawPluginCommandDefinition {
85
- name: string;
86
- description: string;
87
- acceptsArgs?: boolean;
88
- requireAuth?: boolean;
89
- handler: (
90
- ctx: OpenClawPluginCommandContext
91
- ) => Promise<{ text: string }> | { text: string };
92
- }
93
-
94
- export interface OpenClawPluginApi {
95
- id?: string;
96
- name?: string;
97
- version?: string;
98
- description?: string;
99
- source?: string;
100
- config?: Record<string, unknown>;
101
- pluginConfig?: Record<string, unknown>;
102
- runtime?: unknown;
103
- logger: PluginLogger;
104
- registerService: (service: OpenClawPluginService) => void;
105
- registerCommand?: (command: OpenClawPluginCommandDefinition) => void;
106
- registerHook?: (
107
- event: string | string[],
108
- handler: (...args: unknown[]) => Promise<unknown> | unknown,
109
- metadata?: Record<string, unknown>
110
- ) => void;
111
- on?: (
112
- event: string,
113
- handler: (...args: unknown[]) => Promise<unknown> | unknown,
114
- options?: {
115
- priority?: number;
116
- }
117
- ) => void;
118
- }
119
-
120
- export function emptyPluginConfigSchema(): OpenClawPluginConfigSchema;
121
- }