@phronesis-io/openclaw-eigenflux 0.0.1 → 0.0.2

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.
@@ -1,374 +0,0 @@
1
- import * as fs from 'fs';
2
- import * as os from 'os';
3
- import * as path from 'path';
4
- import type { OpenClawPluginApi } from 'openclaw/plugin-sdk';
5
- import { Logger } from './logger';
6
- import { EigenFluxNotifier } from './notifier';
7
-
8
- function createLogger(): Logger {
9
- return new Logger({
10
- info: jest.fn(),
11
- warn: jest.fn(),
12
- error: jest.fn(),
13
- debug: jest.fn(),
14
- });
15
- }
16
-
17
- function createApi(overrides: Partial<OpenClawPluginApi> = {}): OpenClawPluginApi {
18
- return {
19
- id: 'eigenflux',
20
- name: 'EigenFlux',
21
- source: '/tmp/eigenflux',
22
- config: {},
23
- pluginConfig: {},
24
- runtime: {} as OpenClawPluginApi['runtime'],
25
- logger: {
26
- info: jest.fn(),
27
- warn: jest.fn(),
28
- error: jest.fn(),
29
- debug: jest.fn(),
30
- },
31
- registerService: jest.fn(),
32
- ...overrides,
33
- } as unknown as OpenClawPluginApi;
34
- }
35
-
36
- function createConfig() {
37
- return {
38
- gatewayUrl: 'ws://127.0.0.1:18789',
39
- sessionKey: 'agent:main:feishu:direct:ou_123',
40
- agentId: 'main',
41
- replyChannel: 'feishu',
42
- replyTo: 'ou_123',
43
- openclawCliBin: 'openclaw',
44
- };
45
- }
46
-
47
- describe('EigenFluxNotifier', () => {
48
- test('prefers runtime.subagent delivery when available', async () => {
49
- const run = jest.fn().mockResolvedValue({ runId: 'run-subagent' });
50
- const sendAgentMessage = jest.fn();
51
-
52
- const notifier = new EigenFluxNotifier(
53
- createApi({
54
- runtime: {
55
- subagent: {
56
- run,
57
- },
58
- } as OpenClawPluginApi['runtime'],
59
- }),
60
- createLogger(),
61
- createConfig(),
62
- () => ({
63
- sendAgentMessage,
64
- }),
65
- jest.fn()
66
- );
67
-
68
- await expect(notifier.deliver('[EIGENFLUX_TEST] payload')).resolves.toBe(true);
69
- expect(run).toHaveBeenCalledWith({
70
- sessionKey: 'agent:main:feishu:direct:ou_123',
71
- message: '[EIGENFLUX_TEST] payload',
72
- deliver: true,
73
- idempotencyKey: expect.any(String),
74
- });
75
- expect(sendAgentMessage).not.toHaveBeenCalled();
76
- });
77
-
78
- test('falls back to gateway rpc agent when runtime.subagent is unavailable', async () => {
79
- const sendAgentMessage = jest.fn().mockResolvedValue({
80
- sessionKey: 'agent:main:feishu:direct:ou_123',
81
- runId: 'run-gateway',
82
- });
83
-
84
- const notifier = new EigenFluxNotifier(
85
- createApi({
86
- runtime: {} as OpenClawPluginApi['runtime'],
87
- }),
88
- createLogger(),
89
- createConfig(),
90
- () => ({
91
- sendAgentMessage,
92
- }),
93
- jest.fn()
94
- );
95
-
96
- await expect(notifier.deliver('[EIGENFLUX_TEST] payload')).resolves.toBe(true);
97
- expect(sendAgentMessage).toHaveBeenCalledWith('[EIGENFLUX_TEST] payload');
98
- });
99
-
100
- test('falls back to runtime command agent when gateway rpc fails', async () => {
101
- const runCommandWithTimeout = jest.fn().mockResolvedValue({
102
- code: 0,
103
- stdout: 'ok',
104
- stderr: '',
105
- });
106
-
107
- const notifier = new EigenFluxNotifier(
108
- createApi({
109
- runtime: {
110
- system: {
111
- runCommandWithTimeout,
112
- },
113
- } as OpenClawPluginApi['runtime'],
114
- }),
115
- createLogger(),
116
- createConfig(),
117
- () => ({
118
- sendAgentMessage: jest.fn().mockRejectedValue(new Error('gateway failed')),
119
- }),
120
- jest.fn()
121
- );
122
-
123
- await expect(notifier.deliver('[EIGENFLUX_TEST] payload')).resolves.toBe(true);
124
- expect(runCommandWithTimeout).toHaveBeenCalledWith(
125
- [
126
- 'openclaw',
127
- 'agent',
128
- '--message',
129
- '[EIGENFLUX_TEST] payload',
130
- '--agent',
131
- 'main',
132
- '--deliver',
133
- '--reply-channel',
134
- 'feishu',
135
- '--reply-to',
136
- 'ou_123',
137
- ],
138
- { timeoutMs: 15000 }
139
- );
140
- });
141
-
142
- test('falls back to runtime heartbeat when command path is unavailable', async () => {
143
- const enqueueSystemEvent = jest.fn().mockReturnValue(true);
144
- const requestHeartbeatNow = jest.fn();
145
- const spawnRunner = jest.fn().mockResolvedValue({
146
- ok: false as const,
147
- mode: 'spawn',
148
- error: 'spawn disabled in test',
149
- });
150
-
151
- const notifier = new EigenFluxNotifier(
152
- createApi({
153
- runtime: {
154
- system: {
155
- enqueueSystemEvent,
156
- requestHeartbeatNow,
157
- },
158
- } as OpenClawPluginApi['runtime'],
159
- }),
160
- createLogger(),
161
- createConfig(),
162
- () => ({
163
- sendAgentMessage: jest.fn().mockRejectedValue(new Error('gateway failed')),
164
- }),
165
- spawnRunner
166
- );
167
-
168
- await expect(notifier.deliver('[EIGENFLUX_TEST] payload')).resolves.toBe(true);
169
- expect(enqueueSystemEvent).toHaveBeenCalledWith('[EIGENFLUX_TEST] payload', {
170
- sessionKey: 'agent:main:feishu:direct:ou_123',
171
- deliveryContext: {
172
- channel: 'feishu',
173
- to: 'ou_123',
174
- },
175
- });
176
- expect(requestHeartbeatNow).toHaveBeenCalledWith({
177
- reason: 'plugin:eigenflux',
178
- coalesceMs: 0,
179
- agentId: 'main',
180
- sessionKey: 'agent:main:feishu:direct:ou_123',
181
- });
182
- });
183
-
184
- test('deliverWithSubagent only uses runtime.subagent', async () => {
185
- const run = jest.fn().mockResolvedValue({ runId: 'run-subagent-only' });
186
- const notifier = new EigenFluxNotifier(
187
- createApi({
188
- runtime: {
189
- subagent: {
190
- run,
191
- },
192
- } as OpenClawPluginApi['runtime'],
193
- }),
194
- createLogger(),
195
- createConfig(),
196
- () => ({
197
- sendAgentMessage: jest.fn(),
198
- }),
199
- jest.fn()
200
- );
201
-
202
- await expect(notifier.deliverWithSubagent('[EIGENFLUX_TEST] payload')).resolves.toBe(true);
203
- expect(run).toHaveBeenCalledTimes(1);
204
- });
205
-
206
- test('resolves the freshest external session route for runtime.subagent', async () => {
207
- const stateDir = fs.mkdtempSync(path.join(os.tmpdir(), 'eigenflux-session-store-'));
208
- const sessionStorePath = path.join(stateDir, 'sessions.json');
209
- fs.writeFileSync(
210
- sessionStorePath,
211
- JSON.stringify({
212
- 'agent:main:main': {
213
- updatedAt: 100,
214
- deliveryContext: { channel: 'webchat' },
215
- },
216
- 'agent:main:feishu:direct:ou_older': {
217
- updatedAt: 200,
218
- deliveryContext: {
219
- channel: 'feishu',
220
- to: 'user:ou_older',
221
- accountId: 'default',
222
- },
223
- },
224
- 'agent:main:feishu:group:oc_latest': {
225
- updatedAt: 300,
226
- deliveryContext: {
227
- channel: 'feishu',
228
- to: 'chat:oc_latest',
229
- accountId: 'default',
230
- },
231
- },
232
- }),
233
- 'utf-8'
234
- );
235
-
236
- const run = jest.fn().mockResolvedValue({ runId: 'run-external-session' });
237
- const notifier = new EigenFluxNotifier(
238
- createApi({
239
- runtime: {
240
- subagent: {
241
- run,
242
- },
243
- } as OpenClawPluginApi['runtime'],
244
- }),
245
- createLogger(),
246
- {
247
- ...createConfig(),
248
- sessionKey: 'main',
249
- replyChannel: undefined,
250
- replyTo: undefined,
251
- replyAccountId: undefined,
252
- sessionStorePath,
253
- },
254
- () => ({
255
- sendAgentMessage: jest.fn(),
256
- }),
257
- jest.fn()
258
- );
259
-
260
- await expect(notifier.deliver('[EIGENFLUX_TEST] payload')).resolves.toBe(true);
261
- expect(run).toHaveBeenCalledWith({
262
- sessionKey: 'agent:main:feishu:group:oc_latest',
263
- message: '[EIGENFLUX_TEST] payload',
264
- deliver: true,
265
- idempotencyKey: expect.any(String),
266
- });
267
-
268
- fs.rmSync(stateDir, { recursive: true, force: true });
269
- });
270
-
271
- test('normalizes explicit reply targets from session store before CLI fallback', async () => {
272
- const stateDir = fs.mkdtempSync(path.join(os.tmpdir(), 'eigenflux-session-store-'));
273
- const sessionStorePath = path.join(stateDir, 'sessions.json');
274
- fs.writeFileSync(
275
- sessionStorePath,
276
- JSON.stringify({
277
- 'agent:main:feishu:direct:ou_123': {
278
- updatedAt: 300,
279
- deliveryContext: {
280
- channel: 'feishu',
281
- to: 'user:ou_123',
282
- accountId: 'default',
283
- },
284
- },
285
- }),
286
- 'utf-8'
287
- );
288
-
289
- const runCommandWithTimeout = jest.fn().mockResolvedValue({
290
- code: 0,
291
- stdout: 'ok',
292
- stderr: '',
293
- });
294
-
295
- const notifier = new EigenFluxNotifier(
296
- createApi({
297
- runtime: {
298
- system: {
299
- runCommandWithTimeout,
300
- },
301
- } as OpenClawPluginApi['runtime'],
302
- }),
303
- createLogger(),
304
- {
305
- ...createConfig(),
306
- sessionKey: 'main',
307
- replyChannel: 'feishu',
308
- replyTo: 'ou_123',
309
- sessionStorePath,
310
- },
311
- () => ({
312
- sendAgentMessage: jest.fn().mockRejectedValue(new Error('gateway failed')),
313
- }),
314
- jest.fn()
315
- );
316
-
317
- await expect(notifier.deliver('[EIGENFLUX_TEST] payload')).resolves.toBe(true);
318
- expect(runCommandWithTimeout).toHaveBeenCalledWith(
319
- [
320
- 'openclaw',
321
- 'agent',
322
- '--message',
323
- '[EIGENFLUX_TEST] payload',
324
- '--agent',
325
- 'main',
326
- '--deliver',
327
- '--reply-channel',
328
- 'feishu',
329
- '--reply-to',
330
- 'user:ou_123',
331
- '--reply-account',
332
- 'default',
333
- ],
334
- { timeoutMs: 15000 }
335
- );
336
-
337
- fs.rmSync(stateDir, { recursive: true, force: true });
338
- });
339
-
340
- test('remembers the resolved route after a successful delivery', async () => {
341
- const workdir = fs.mkdtempSync(path.join(os.tmpdir(), 'eigenflux-notifier-memory-'));
342
- const run = jest.fn().mockResolvedValue({ runId: 'run-subagent-memory' });
343
- const notifier = new EigenFluxNotifier(
344
- createApi({
345
- runtime: {
346
- subagent: {
347
- run,
348
- },
349
- } as OpenClawPluginApi['runtime'],
350
- }),
351
- createLogger(),
352
- {
353
- ...createConfig(),
354
- workdir,
355
- },
356
- () => ({
357
- sendAgentMessage: jest.fn(),
358
- }),
359
- jest.fn()
360
- );
361
-
362
- await expect(notifier.deliver('[EIGENFLUX_TEST] payload')).resolves.toBe(true);
363
-
364
- const remembered = JSON.parse(
365
- fs.readFileSync(path.join(workdir, 'session.json'), 'utf-8')
366
- ) as Record<string, unknown>;
367
- expect(remembered.sessionKey).toBe('agent:main:feishu:direct:ou_123');
368
- expect(remembered.agentId).toBe('main');
369
- expect(remembered.replyChannel).toBe('feishu');
370
- expect(remembered.replyTo).toBe('ou_123');
371
-
372
- fs.rmSync(workdir, { recursive: true, force: true });
373
- });
374
- });