@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
@@ -1,437 +0,0 @@
1
- import * as fs from 'fs';
2
- import * as os from 'os';
3
- import * as path from 'path';
4
- import http from 'http';
5
- import { WebSocketServer } from 'ws';
6
-
7
- function waitFor(condition: () => boolean, timeoutMs = 8000): Promise<void> {
8
- const startedAt = Date.now();
9
- return new Promise((resolve, reject) => {
10
- const timer = setInterval(() => {
11
- if (condition()) {
12
- clearInterval(timer);
13
- resolve();
14
- return;
15
- }
16
- if (Date.now() - startedAt > timeoutMs) {
17
- clearInterval(timer);
18
- reject(new Error('condition wait timeout'));
19
- }
20
- }, 50);
21
- });
22
- }
23
-
24
- describe('register integration', () => {
25
- let homeDir: string;
26
- let originalHome: string | undefined;
27
- let workdir: string;
28
-
29
- let apiHttpServer: http.Server;
30
- let apiPort: number;
31
- let apiRequestCount: number;
32
- let apiAuthHeader: string | undefined;
33
- let apiUserAgentHeader: string | undefined;
34
- let apiFeedItems: Array<{
35
- item_id: string;
36
- group_id?: string;
37
- broadcast_type: string;
38
- updated_at: number;
39
- }>;
40
-
41
- let gatewayHttpServer: http.Server;
42
- let gatewayWss: WebSocketServer;
43
- let gatewayPort: number;
44
-
45
- beforeEach(async () => {
46
- originalHome = process.env.HOME;
47
- homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'eigenflux-openclaw-home-'));
48
- process.env.HOME = homeDir;
49
- workdir = fs.mkdtempSync(path.join(os.tmpdir(), 'eigenflux-openclaw-workdir-'));
50
- fs.writeFileSync(
51
- path.join(workdir, 'credentials.json'),
52
- JSON.stringify({ access_token: 'at_integration_token' }),
53
- 'utf-8'
54
- );
55
-
56
- apiRequestCount = 0;
57
- apiAuthHeader = undefined;
58
- apiUserAgentHeader = undefined;
59
- apiFeedItems = [
60
- {
61
- item_id: '501',
62
- group_id: 'group-int-1',
63
- broadcast_type: 'info',
64
- updated_at: 1760000000000,
65
- },
66
- ];
67
- apiHttpServer = http.createServer((req, res) => {
68
- if (req.url?.startsWith('/api/v1/items/feed')) {
69
- apiRequestCount++;
70
- apiAuthHeader = req.headers.authorization;
71
- apiUserAgentHeader = req.headers['user-agent'];
72
- res.writeHead(200, { 'Content-Type': 'application/json' });
73
- res.end(
74
- JSON.stringify({
75
- code: 0,
76
- msg: 'success',
77
- data: {
78
- items: apiFeedItems,
79
- has_more: false,
80
- notifications: [],
81
- },
82
- })
83
- );
84
- return;
85
- }
86
-
87
- res.writeHead(404);
88
- res.end();
89
- });
90
- await new Promise<void>((resolve) => {
91
- apiHttpServer.listen(0, '127.0.0.1', () => {
92
- apiPort = (apiHttpServer.address() as any).port;
93
- resolve();
94
- });
95
- });
96
-
97
- gatewayHttpServer = http.createServer();
98
- gatewayWss = new WebSocketServer({ server: gatewayHttpServer });
99
- await new Promise<void>((resolve) => {
100
- gatewayHttpServer.listen(0, '127.0.0.1', () => {
101
- gatewayPort = (gatewayHttpServer.address() as any).port;
102
- resolve();
103
- });
104
- });
105
- });
106
-
107
- afterEach(async () => {
108
- if (originalHome === undefined) {
109
- delete process.env.HOME;
110
- } else {
111
- process.env.HOME = originalHome;
112
- }
113
- fs.rmSync(homeDir, { recursive: true, force: true });
114
- fs.rmSync(workdir, { recursive: true, force: true });
115
- await new Promise<void>((resolve) => apiHttpServer.close(() => resolve()));
116
- await new Promise<void>((resolve) => gatewayWss.close(() => resolve()));
117
- await new Promise<void>((resolve) => gatewayHttpServer.close(() => resolve()));
118
- });
119
-
120
- test('falls back to gateway rpc agent when polling feed returns new items', async () => {
121
- jest.resetModules();
122
- const sessionStorePath = path.join(
123
- homeDir,
124
- '.openclaw',
125
- 'agents',
126
- 'main',
127
- 'sessions',
128
- 'sessions.json'
129
- );
130
- const { default: plugin } = await import('./index');
131
- const services: any[] = [];
132
- const gatewayMethods: string[] = [];
133
- const agentParams: any[] = [];
134
-
135
- gatewayWss.on('connection', (socket) => {
136
- socket.send(
137
- JSON.stringify({
138
- type: 'event',
139
- event: 'connect.challenge',
140
- payload: { nonce: 'nonce-integration' },
141
- })
142
- );
143
-
144
- socket.on('message', (raw) => {
145
- const frame = JSON.parse(raw.toString());
146
- gatewayMethods.push(String(frame.method || ''));
147
-
148
- if (frame.type !== 'req') {
149
- return;
150
- }
151
-
152
- if (frame.method === 'connect') {
153
- socket.send(
154
- JSON.stringify({
155
- type: 'res',
156
- id: frame.id,
157
- ok: true,
158
- payload: {
159
- type: 'hello-ok',
160
- protocol: 3,
161
- server: { version: 'test', connId: 'conn-test' },
162
- features: { methods: ['agent'], events: [] },
163
- snapshot: { ts: Date.now() },
164
- policy: { maxPayload: 1000000, maxBufferedBytes: 1000000, tickIntervalMs: 30000 },
165
- },
166
- })
167
- );
168
- return;
169
- }
170
-
171
- if (frame.method === 'agent') {
172
- agentParams.push(frame.params);
173
- socket.send(
174
- JSON.stringify({
175
- type: 'res',
176
- id: frame.id,
177
- ok: true,
178
- payload: {
179
- runId: 'run-integration-1',
180
- status: 'started',
181
- },
182
- })
183
- );
184
- }
185
- });
186
- });
187
-
188
- plugin.register({
189
- config: {
190
- gateway: {
191
- auth: {
192
- token: 'gw_test_token',
193
- },
194
- },
195
- },
196
- pluginConfig: {
197
- gatewayUrl: `ws://127.0.0.1:${gatewayPort}`,
198
- servers: [
199
- {
200
- name: 'eigenflux',
201
- endpoint: `http://127.0.0.1:${apiPort}`,
202
- workdir,
203
- pollInterval: 60,
204
- sessionStorePath,
205
- },
206
- ],
207
- },
208
- runtime: {},
209
- logger: { info: jest.fn(), warn: jest.fn(), error: jest.fn(), debug: jest.fn() },
210
- registerService: (service: any) => {
211
- services.push(service);
212
- },
213
- } as any);
214
-
215
- expect(services).toHaveLength(1);
216
- await services[0].start();
217
- await waitFor(() => agentParams.length === 1);
218
-
219
- expect(apiRequestCount).toBeGreaterThanOrEqual(1);
220
- expect(apiAuthHeader).toBe('Bearer at_integration_token');
221
- expect(apiUserAgentHeader).toContain('eigenflux-plugin');
222
- expect(apiUserAgentHeader).toContain('node/');
223
- expect(gatewayMethods).toEqual(['connect', 'agent']);
224
- expect(agentParams[0]).toEqual(
225
- expect.objectContaining({
226
- agentId: 'main',
227
- sessionKey: 'main',
228
- message: expect.stringContaining('[EIGENFLUX_FEED_PAYLOAD]'),
229
- deliver: true,
230
- })
231
- );
232
- expect(String(agentParams[0].message)).toContain('"item_id": "501"');
233
- expect(String(agentParams[0].message)).toContain('"group_id": "group-int-1"');
234
- expect(String(agentParams[0].message)).toContain('network=eigenflux');
235
- expect(String(agentParams[0].message)).toContain(`workdir=${workdir}`);
236
- expect(String(agentParams[0].message)).toContain(
237
- `skill_file=http://127.0.0.1:${apiPort}/skill.md`
238
- );
239
- expect(String(agentParams[0].message)).toContain(
240
- 'submit the corresponding feedback scores through the normal EigenFlux workflow'
241
- );
242
- expect(typeof agentParams[0].idempotencyKey).toBe('string');
243
- expect(agentParams[0].idempotencyKey.length).toBeGreaterThan(0);
244
-
245
- await services[0].stop();
246
- });
247
-
248
- test('dispatches the entire feed payload in a single gateway agent message', async () => {
249
- apiFeedItems = [
250
- {
251
- item_id: '601',
252
- group_id: 'group-dup-1',
253
- broadcast_type: 'info',
254
- updated_at: 1760000000100,
255
- },
256
- {
257
- item_id: '602',
258
- group_id: 'group-dup-1',
259
- broadcast_type: 'info',
260
- updated_at: 1760000000200,
261
- },
262
- ];
263
-
264
- jest.resetModules();
265
- const sessionStorePath = path.join(
266
- homeDir,
267
- '.openclaw',
268
- 'agents',
269
- 'main',
270
- 'sessions',
271
- 'sessions.json'
272
- );
273
- const { default: plugin } = await import('./index');
274
- const services: any[] = [];
275
- const agentParams: any[] = [];
276
-
277
- gatewayWss.on('connection', (socket) => {
278
- socket.send(
279
- JSON.stringify({
280
- type: 'event',
281
- event: 'connect.challenge',
282
- payload: { nonce: 'nonce-duplicate-group' },
283
- })
284
- );
285
-
286
- socket.on('message', (raw) => {
287
- const frame = JSON.parse(raw.toString());
288
- if (frame.type !== 'req') {
289
- return;
290
- }
291
-
292
- if (frame.method === 'connect') {
293
- socket.send(
294
- JSON.stringify({
295
- type: 'res',
296
- id: frame.id,
297
- ok: true,
298
- payload: {
299
- type: 'hello-ok',
300
- protocol: 3,
301
- server: { version: 'test', connId: 'conn-dup' },
302
- features: { methods: ['agent'], events: [] },
303
- snapshot: { ts: Date.now() },
304
- policy: { maxPayload: 1000000, maxBufferedBytes: 1000000, tickIntervalMs: 30000 },
305
- },
306
- })
307
- );
308
- return;
309
- }
310
-
311
- if (frame.method === 'agent') {
312
- agentParams.push(frame.params);
313
- socket.send(
314
- JSON.stringify({
315
- type: 'res',
316
- id: frame.id,
317
- ok: true,
318
- payload: {
319
- runId: `run-dup-${agentParams.length}`,
320
- status: 'started',
321
- },
322
- })
323
- );
324
- }
325
- });
326
- });
327
-
328
- plugin.register({
329
- config: {
330
- gateway: {
331
- auth: {
332
- token: 'gw_test_token',
333
- },
334
- },
335
- },
336
- pluginConfig: {
337
- gatewayUrl: `ws://127.0.0.1:${gatewayPort}`,
338
- servers: [
339
- {
340
- name: 'eigenflux',
341
- endpoint: `http://127.0.0.1:${apiPort}`,
342
- workdir,
343
- pollInterval: 60,
344
- sessionStorePath,
345
- },
346
- ],
347
- },
348
- runtime: {},
349
- logger: { info: jest.fn(), warn: jest.fn(), error: jest.fn(), debug: jest.fn() },
350
- registerService: (service: any) => {
351
- services.push(service);
352
- },
353
- } as any);
354
-
355
- expect(services).toHaveLength(1);
356
- await services[0].start();
357
- await waitFor(() => agentParams.length === 1);
358
-
359
- expect(agentParams).toHaveLength(1);
360
- expect(String(agentParams[0].message)).toContain('"item_id": "601"');
361
- expect(String(agentParams[0].message)).toContain('"item_id": "602"');
362
- expect(String(agentParams[0].message)).toContain('"group_id": "group-dup-1"');
363
-
364
- await services[0].stop();
365
- });
366
-
367
- test('routes mocked feed notifications to the freshest external session for runtime.subagent', async () => {
368
- jest.resetModules();
369
- const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'eigenflux-home-'));
370
- const sessionStoreDir = path.join(homeDir, '.openclaw', 'agents', 'main', 'sessions');
371
- fs.mkdirSync(sessionStoreDir, { recursive: true });
372
- const sessionStorePath = path.join(sessionStoreDir, 'sessions.json');
373
- fs.writeFileSync(
374
- sessionStorePath,
375
- JSON.stringify({
376
- 'agent:main:main': {
377
- updatedAt: 100,
378
- deliveryContext: {
379
- channel: 'webchat',
380
- },
381
- },
382
- 'agent:main:feishu:direct:ou_feed_target': {
383
- updatedAt: 200,
384
- deliveryContext: {
385
- channel: 'feishu',
386
- to: 'user:ou_feed_target',
387
- accountId: 'default',
388
- },
389
- },
390
- }),
391
- 'utf-8'
392
- );
393
-
394
- const { default: plugin } = await import('./index');
395
- const services: any[] = [];
396
- const subagentRun = jest.fn().mockResolvedValue({ runId: 'run-subagent-feed' });
397
-
398
- plugin.register({
399
- config: {},
400
- pluginConfig: {
401
- servers: [
402
- {
403
- name: 'eigenflux',
404
- endpoint: `http://127.0.0.1:${apiPort}`,
405
- workdir,
406
- pollInterval: 60,
407
- sessionStorePath,
408
- },
409
- ],
410
- },
411
- runtime: {
412
- subagent: {
413
- run: subagentRun,
414
- },
415
- },
416
- logger: { info: jest.fn(), warn: jest.fn(), error: jest.fn(), debug: jest.fn() },
417
- registerService: (service: any) => {
418
- services.push(service);
419
- },
420
- } as any);
421
-
422
- expect(services).toHaveLength(1);
423
- await services[0].start();
424
- await waitFor(() => subagentRun.mock.calls.length === 1);
425
-
426
- expect(subagentRun).toHaveBeenCalledWith({
427
- sessionKey: 'agent:main:feishu:direct:ou_feed_target',
428
- message: expect.stringContaining('[EIGENFLUX_FEED_PAYLOAD]'),
429
- deliver: true,
430
- idempotencyKey: expect.any(String),
431
- });
432
- expect(String(subagentRun.mock.calls[0]?.[0]?.message)).toContain('"item_id": "501"');
433
-
434
- await services[0].stop();
435
- fs.rmSync(homeDir, { recursive: true, force: true });
436
- });
437
- });